C# Language
Деревья выражений
Поиск…
Вступление
Синтаксис
- Выражение <TDelegate> name = lambdaExpression;
параметры
параметр | подробности |
---|---|
TDelegate | Тип делегата, который будет использоваться для выражения |
lambdaExpression | Выражение лямбда (например, num => num < 5 ) |
замечания
Введение в деревья выражений
Откуда мы пришли
Деревья выражений - все о потреблении «исходного кода» во время выполнения. Рассмотрим метод, который рассчитывает налог с продаж, причитающийся по заказу клиента decimal CalculateTotalTaxDue(SalesOrder order)
. Использование этого метода в .NET-программе легко - просто назовите его decimal taxDue = CalculateTotalTaxDue(order);
, Что делать, если вы хотите применить его ко всем результатам удаленного запроса (SQL, XML, удаленный сервер и т. Д.)? Эти источники удаленного запроса не могут вызвать метод! Традиционно вам придется инвертировать поток во всех этих случаях. Сделайте весь запрос, сохраните его в памяти, затем просмотрите результаты и рассчитайте налог за каждый результат.
Как избежать ошибок инверсии потока и проблем с задержкой
Деревья выражений - это структуры данных в формате дерева, где каждый узел содержит выражение. Они используются для перевода скомпилированных инструкций (например, методов, используемых для фильтрации данных) в выражениях, которые могут использоваться вне программной среды, например внутри запроса к базе данных.
Проблема здесь в том, что удаленный запрос не может получить доступ к нашему методу . Мы могли бы избежать этой проблемы, если вместо этого мы отправили инструкции для метода в удаленный запрос. В нашем примере CalculateTotalTaxDue
это означает, что мы отправляем эту информацию:
- Создать переменную для хранения общего налога
- Прокрутите все строки по порядку
- Для каждой строки проверьте, облагается ли продукт
- Если это так, умножьте общую сумму по применимой ставке налога и добавьте эту сумму к общей сумме
- В противном случае ничего не делать
С помощью этих инструкций удаленный запрос может выполнять работу по мере создания данных.
Для этого есть две проблемы. Как преобразовать скомпилированный метод .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.
- Напишите запрос, используя выражение лямбда:
products.Where(x => x.Cost > 5)
- Компилятор преобразует это выражение в дерево выражений с инструкциями «проверьте, является ли свойство Cost параметра более пяти».
- Поставщик запроса анализирует дерево выражений и создает корректный SQL-запрос
SELECT * FROM products WHERE Cost > 5
- 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
. На простом английском языке дайте краткое описание шагов, которые необходимо предпринять для создания дерева.
- Проверьте, облагается ли товар
- Если это так, умножьте общую сумму по применимой ставке налога и верните эту сумму
- В противном случае верните 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).
Деревья выражений могут быть созданы через
- Анонимное выражение лямбда,
- Вручную, используя пространство имен System.Linq.Expressions.
Деревья выражений из лямбда-выражений
Когда выражение lambda назначается переменной типа Expression, компилятор испускает код для построения дерева выражений, которое представляет собой выражение лямбда.
В следующих примерах кода показано, как компилятор C # создает дерево выражений, которое представляет выражение лямбда num => num <5.
Expression<Func<int, bool>> lambda = num => num < 5;
Деревья выражений с помощью API
Деревья выражений также создаются с использованием класса Expression . Этот класс содержит статические заводские методы, которые создают узлы дерева выражений определенных типов.
Ниже несколько типов узлов дерева.
- ParameterExpression
- 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);