Подтвердить что ты не робот

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

Я работаю с деревьями выражений уже несколько дней, и мне любопытно узнать, что делает 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)
}
4b9b3361

Ответ 1

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

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

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

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

Ответ 2

Немного разобравшись, я обнаружил, что Expression.CanReduce всегда reutrns 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: { ... }  

Ответ 3

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

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

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

    x += y

    становится

    x = x + y.

  • Операторы предварительного инкремента и пост-инкремента расширяются до их дискретных операций. Для pre-increment/decments,

    ++x

    становится приблизительно:

    x = x + 1

    и

    x++

    становится приблизительно:

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

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

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

    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. рамки.

Ответ 4

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

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

Ответ 5

im угадывает его больше для разных провайдеров linq, чтобы использовать их для преобразования определенных типов node в более простое представление ast.

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

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