Что делает Expression.Reduce ()? - PullRequest
       22

Что делает Expression.Reduce ()?

34 голосов
/ 11 января 2010

Я работаю с деревьями выражений уже несколько дней, и мне любопытно узнать, что делает Expression.Reduce (). Документация msdn не очень полезна, так как она только утверждает, что она "уменьшает" выражение. На всякий случай я попробовал пример (см. Ниже), чтобы проверить, включает ли этот метод математическое сокращение, но это не так.

Кто-нибудь знает, что делает этот метод, и можно ли привести быстрый пример, показывающий его в действии? Есть какие-нибудь хорошие ресурсы?

static void Main(string[] args)
{
    Expression<Func<double, double>> func = x => (x + x + x) + Math.Exp(x + x + x);
    Console.WriteLine(func);
    Expression r_func = func.Reduce();
    Console.WriteLine(r_func); // This prints out the same as Console.WriteLine(func)
}

Ответы [ 5 ]

29 голосов
/ 11 января 2010

Документ, на который вам нужно посмотреть: expr-tree-spec.doc .

Это спецификация для деревьев выражений. Прочитайте разделы «2.2 Reducible Nodes» и «4.3.5 Reduce Method».

По сути, этот метод предназначен для людей, которые внедряют или переносят свои динамические языки в .NET. Так что они могут создавать свои собственные узлы, которые могут «сводиться» к узлам дерева стандартных выражений и могут быть скомпилированы. В API деревьев выражений есть некоторые «сводимые» узлы, но я не знаю, можно ли получить какие-либо практические примеры (поскольку все стандартные узлы выражений компилируются так или иначе, как конечному пользователю, вам, вероятно, все равно, сокращены ли они) "за кадром или нет).

Да, документация MSDN является очень базовой в этой области, потому что основным источником информации и документов для языковых разработчиков является http://dlr.codeplex.com/

23 голосов
/ 11 января 2010

После небольшой разборки я обнаружил, что Expression.CanReduce всегда возвращает false, а Expression.Reduce () всегда возвращает this. Тем не менее, есть несколько типов, которые перекрывают оба. LambdaExpression наследует реализации по умолчанию, что объясняет, почему выражения, которые были опробованы до сих пор, не работают.

Одним из типов, который переопределяет Reduce (), является MemberInitExpression, что привело меня к следующему успешному эксперименту:

class ReduceFinder : ExpressionVisitor {
    public override Expression Visit(Expression node) {
        if (node != null && node.CanReduce) {
            var reduced = node.Reduce();
            Console.WriteLine("Found expression to reduce!");
            Console.WriteLine("Before: {0}: {1}", node.GetType().Name, node);
            Console.WriteLine("After: {0}: {1}", reduced.GetType().Name, reduced);
        }
        return base.Visit(node);
    }
}

class Foo {
    public int x;
    public int y;
}

static class Program {
    static void Main() {
        Expression<Func<int, Foo>> expr = z => new Foo { x = (z + 1), y = (z + 1) };
        new ReduceFinder().Visit(expr);
    }
}

Выход:

Found expression to reduce!  
Before: MemberInitExpression: new Foo() {x = (z + 1), y = (z + 1)}  
After: ScopeN: { ... }  
6 голосов
/ 24 ноября 2015

Это довольно старый вопрос, но он, кажется, немного интересен, поэтому я добавляю этот дополнительный ответ с информацией о том, что готовые вещи .NET делают на данный момент.

Насколько я могу судить, Reduce () переопределяется только в сложных операциях, которые реализуют назначение как часть своей работы. Кажется, есть три ключевых сценария.

  1. Составные присваивания расширены до дискретных двоичных арифметических операций и операций присваивания; другими словами,

    x += y

    становится

    x = x + y.

  2. Операторы преинкремента и постинкремента расширены до их дискретных операций. Для предварительного увеличения / уменьшения,

    ++x

    становится примерно:

    x = x + 1

    и

    x++

    становится примерно:

    temp = x;
    x = x + 1;
    temp;
    

    Я говорю примерно, потому что операция не реализована в виде двоичной операции x + 1 с левым операндом x и правым операндом с константой 1, но как унарная операция увеличения / уменьшения. Чистый эффект тот же.

  3. Инициализаторы членов и списков расширены от их краткой формы до их полной формы. Итак:

    new Thing() { Param1 = 4, Param2 = 5 }

    становится:

    temp = new Thing();
    temp.Param1 = 4;
    temp.Param2 = 5;
    temp;
    

    и

    new List<int>() { 4, 5 }

    становится:

    temp = new List<int>();
    temp.Add(4);
    temp.Add(5);
    temp;
    

Делают ли эти изменения более легкими или трудными для человека реализацию чего-либо, анализирующего дерево выражений, это вопрос мнений, но суть в том, что это уровень сокращения, который, кажется, выходит за рамки стандартного .NET основа.

2 голосов
/ 24 июня 2014

В дополнение к ответу Ника Геррера я нашел следующие выражения, которые переопределили метод CanReduce:

* Обозначает внутренний производный тип BinaryExpression в соответствии с JustDecompile

1 голос
/ 11 января 2010

Я предполагаю, что для разных поставщиков linq лучше использовать их для преобразования определенных типов узлов в более простое представление ast.

, так как документы скудны, может использоваться для устранения общего подвыражения для устранения избыточных выражений. если ваша функция вычисляла x + x более одного раза без изменения локального x, вы могли бы упростить его, сохранив результат первого выражения во временное. может быть, это было бы до поставщика linq, чтобы дополнительно реализовать эти преобразования.

или если вы вложили BlockExpressions без кода (выражение типа {{{}}}), их можно удалить или пустое выражение ConditionalExpression ...

...