Поиск…


Вступление

Деревья выражений - это выражения, расположенные в древовидной структуре данных. Каждый узел в дереве является представлением выражения, причем выражение является кодом. Представление Lambda с внутренней памятью было бы деревом выражений, которое содержит фактические элементы (то есть код) запроса, но не его результат. Деревья выражений делают структуру лямбда-выражения прозрачной и явной.

Синтаксис

  • Выражение <TDelegate> name = lambdaExpression;

параметры

параметр подробности
TDelegate Тип делегата, который будет использоваться для выражения
lambdaExpression Выражение лямбда (например, num => num < 5 )

замечания

Введение в деревья выражений

Откуда мы пришли

Деревья выражений - все о потреблении «исходного кода» во время выполнения. Рассмотрим метод, который рассчитывает налог с продаж, причитающийся по заказу клиента decimal CalculateTotalTaxDue(SalesOrder order) . Использование этого метода в .NET-программе легко - просто назовите его decimal taxDue = CalculateTotalTaxDue(order); , Что делать, если вы хотите применить его ко всем результатам удаленного запроса (SQL, XML, удаленный сервер и т. Д.)? Эти источники удаленного запроса не могут вызвать метод! Традиционно вам придется инвертировать поток во всех этих случаях. Сделайте весь запрос, сохраните его в памяти, затем просмотрите результаты и рассчитайте налог за каждый результат.

Как избежать ошибок инверсии потока и проблем с задержкой

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

Проблема здесь в том, что удаленный запрос не может получить доступ к нашему методу . Мы могли бы избежать этой проблемы, если вместо этого мы отправили инструкции для метода в удаленный запрос. В нашем примере CalculateTotalTaxDue это означает, что мы отправляем эту информацию:

  1. Создать переменную для хранения общего налога
  2. Прокрутите все строки по порядку
  3. Для каждой строки проверьте, облагается ли продукт
  4. Если это так, умножьте общую сумму по применимой ставке налога и добавьте эту сумму к общей сумме
  5. В противном случае ничего не делать

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

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

Без деревьев выражений вы могли бы решить первую проблему с MSIL. (MSIL - это ассемблерный код, созданный компилятором .NET.) Разбор MSIL возможен , но это непросто. Даже если вы правильно разобрали его, может быть трудно определить, какова была цель оригинального программиста с конкретной процедурой.

Деревья выражений сохраняют день

Деревья выражений адресуют эти точные проблемы. Они представляют собой программные инструкции для структуры данных дерева, где каждый узел представляет одну инструкцию и имеет ссылки на всю информацию, необходимую для выполнения этой инструкции. Например, MethodCallExpression имеет отношение к 1) MethodInfo он собирается позвонить, 2) список Expression S будет переходить к этому методу, 3) для методов экземпляра, то Expression вы будете вызывать метод. Вы можете «ходить по дереву» и применять инструкции к вашему удаленному запросу.

Создание деревьев выражений

Самый простой способ создать дерево выражений - с помощью выражения лямбда. Эти выражения выглядят почти так же, как обычные методы C #. Важно понимать, что это магия компилятора . Когда вы сначала создаете лямбда-выражение, компилятор проверяет, к чему вы его назначили. Если это тип Delegate (включая Action или Func ), компилятор преобразует лямбда-выражение в делегат. Если это LambdaExpression (или Expression<Action<T>> или Expression<Func<T>> которое строго типизировано LambdaExpression ), компилятор преобразует его в LambdaExpression . Это - то, где волшебство начинает. За кулисами, компилятор использует API дерева выражений, чтобы преобразовать Ваше lambda выражение в LambdaExpression .

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

ПРИМЕЧАНИЕ. Названия здесь немного запутывают. Лямбда-выражение (два слова, нижний регистр) относится к блоку кода с индикатором a => . Он представляет анонимный метод в C # и преобразуется в Delegate или Expression . LambdaExpression (одно слово, PascalCase) относится к типу узла в Expression API, который представляет собой метод, который вы можете выполнить.

Деревья выражений и LINQ

Одним из наиболее распространенных применений деревьев выражений является запрос LINQ и базы данных. LINQ создает дерево выражений с поставщиком запросов для применения ваших инструкций к целевому удаленному запросу. Например, поставщик запросов LINQ to Entity Framework преобразует дерево выражений в SQL, который выполняется непосредственно из базы данных.

Объединяя все части, вы можете увидеть реальную силу LINQ.

  1. Напишите запрос, используя выражение лямбда: products.Where(x => x.Cost > 5)
  2. Компилятор преобразует это выражение в дерево выражений с инструкциями «проверьте, является ли свойство Cost параметра более пяти».
  3. Поставщик запроса анализирует дерево выражений и создает корректный SQL-запрос SELECT * FROM products WHERE Cost > 5
  4. ORM реализует все результаты в POCOs, и вы получаете список объектов назад

Заметки

  • Деревья выражений неизменяемы. Если вы хотите изменить дерево выражений, вам нужно создать новый, скопировать существующий в новый (чтобы пересечь дерево выражений, вы можете использовать ExpressionVisitor ) и внести нужные изменения.

Создание деревьев выражений с помощью API

using System.Linq.Expressions;

// Manually build the expression tree for 
// the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
    Expression.Lambda<Func<int, bool>>(
        numLessThanFive,
        new ParameterExpression[] { numParam });

Компиляция деревьев выражений

// Define an expression tree, taking an integer, returning a bool.
Expression<Func<int, bool>> expr = num => num < 5;

// Call the Compile method on the expression tree to return a delegate that can be called.
Func<int, bool> result = expr.Compile();

// Invoke the delegate and write the result to the console.
Console.WriteLine(result(4)); // Prints true

// Prints True.

// You can also combine the compile step with the call/invoke step as below:
Console.WriteLine(expr.Compile()(4));

Разбор выражений деревьев

using System.Linq.Expressions;

// Create an expression tree.
Expression<Func<int, bool>> exprTree = num => num < 5;

// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;

Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
                  param.Name, left.Name, operation.NodeType, right.Value);

// Decomposed expression: num => num LessThan 5      

Создание деревьев выражений с выражением лямбда

Следующее - это самое основное дерево выражений, созданное лямбдой.

Expression<Func<int, bool>> lambda = num => num == 42;

Чтобы создать деревья выражений «вручную», следует использовать класс Expression .

Вышеприведенное выражение эквивалентно:

ParameterExpression parameter = Expression.Parameter(typeof(int), "num"); // num argument
ConstantExpression constant = Expression.Constant(42, typeof(int)); // 42 constant
BinaryExpression equality = Expression.Equals(parameter, constant); // equality of two expressions (num == 42)
Expression<Func<int, bool>> lambda = Expression.Lambda<Func<int, bool>>(equality, parameter);

Понимание выражений API

Мы будем использовать API дерева выражений для создания дерева CalculateSalesTax . На простом английском языке дайте краткое описание шагов, которые необходимо предпринять для создания дерева.

  1. Проверьте, облагается ли товар
  2. Если это так, умножьте общую сумму по применимой ставке налога и верните эту сумму
  3. В противном случае верните 0
//For reference, we're using the API to build this lambda expression
    orderLine => orderLine.IsTaxable ? orderLine.Total * orderLine.Order.TaxRate : 0;

//The orderLine parameter we pass in to the method.  We specify it's type (OrderLine) and the name of the parameter.
    ParameterExpression orderLine = Expression.Parameter(typeof(OrderLine), "orderLine");

//Check if the parameter is taxable;  First we need to access the is taxable property, then check if it's true
    PropertyInfo isTaxableAccessor = typeof(OrderLine).GetProperty("IsTaxable");
    MemberExpression getIsTaxable = Expression.MakeMemberAccess(orderLine, isTaxableAccessor);
    UnaryExpression isLineTaxable = Expression.IsTrue(getIsTaxable);

//Before creating the if, we need to create the braches
    //If the line is taxable, we'll return the total times the tax rate; get the total and tax rate, then multiply
    //Get the total
    PropertyInfo totalAccessor = typeof(OrderLine).GetProperty("Total");
    MemberExpression getTotal = Expression.MakeMemberAccess(orderLine, totalAccessor);
    
    //Get the order
    PropertyInfo orderAccessor = typeof(OrderLine).GetProperty("Order");
    MemberExpression getOrder = Expression.MakeMemberAccess(orderLine, orderAccessor);
    
    //Get the tax rate - notice that we pass the getOrder expression directly to the member access
    PropertyInfo taxRateAccessor = typeof(Order).GetProperty("TaxRate");
    MemberExpression getTaxRate = Expression.MakeMemberAccess(getOrder, taxRateAccessor);
    
    //Multiply the two - notice we pass the two operand expressions directly to multiply
    BinaryExpression multiplyTotalByRate = Expression.Multiply(getTotal, getTaxRate);
    
//If the line is not taxable, we'll return a constant value - 0.0 (decimal)
    ConstantExpression zero = Expression.Constant(0M);

//Create the actual if check and branches
    ConditionalExpression ifTaxableTernary = Expression.Condition(isLineTaxable, multiplyTotalByRate, zero);
    
//Wrap the whole thing up in a "method" - a LambdaExpression
    Expression<Func<OrderLine, decimal>> method = Expression.Lambda<Func<OrderLine, decimal>>(ifTaxableTernary, orderLine);

Дерево выражений Basic

Деревья выражений представляют код в древовидной структуре данных, где каждый узел является выражением

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

Они также используются в динамической языковой версии (DLR) для обеспечения взаимодействия между динамическими языками и .NET Framework и позволяют разработчикам компилятора генерировать деревья выражений вместо промежуточного языка Microsoft (MSIL).

Деревья выражений могут быть созданы через

  1. Анонимное выражение лямбда,
  2. Вручную, используя пространство имен System.Linq.Expressions.

Деревья выражений из лямбда-выражений

Когда выражение lambda назначается переменной типа Expression, компилятор испускает код для построения дерева выражений, которое представляет собой выражение лямбда.

В следующих примерах кода показано, как компилятор C # создает дерево выражений, которое представляет выражение лямбда num => num <5.

Expression<Func<int, bool>> lambda = num => num < 5;

Деревья выражений с помощью API

Деревья выражений также создаются с использованием класса Expression . Этот класс содержит статические заводские методы, которые создают узлы дерева выражений определенных типов.

Ниже несколько типов узлов дерева.

  1. ParameterExpression
  2. MethodCallExpression

В следующем примере кода показано, как создать дерево выражений, которое представляет выражение lambda num => num <5 с помощью API.

ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 = Expression.Lambda<Func<int, bool>>(numLessThanFive,new ParameterExpression[] { numParam });

Изучение структуры выражения с использованием посетителя

Определите новый класс посетителя, переопределив некоторые из методов ExpressionVisitor :

class PrintingVisitor : ExpressionVisitor {
    protected override Expression VisitConstant(ConstantExpression node) {
        Console.WriteLine("Constant: {0}", node);
        return base.VisitConstant(node);
    }
    protected override Expression VisitParameter(ParameterExpression node) {
        Console.WriteLine("Parameter: {0}", node);
        return base.VisitParameter(node);
    }
    protected override Expression VisitBinary(BinaryExpression node) {
        Console.WriteLine("Binary with operator {0}", node.NodeType);
        return base.VisitBinary(node);
    }
}

Позвоните в Visit чтобы использовать этого посетителя в существующем выражении:

Expression<Func<int,bool>> isBig = a => a > 1000000;
var visitor = new PrintingVisitor();
visitor.Visit(isBig);


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow