Szukaj…


Wprowadzenie

Drzewa wyrażeń to wyrażenia ułożone w treelike struktury danych. Każdy węzeł w drzewie jest reprezentacją wyrażenia, przy czym wyrażenie jest kodem. Reprezentacja wyrażenia Lambda w pamięci byłaby drzewem wyrażeń, które zawiera rzeczywiste elementy (tj. Kod) zapytania, ale nie jego wynik. Drzewa wyrażeń sprawiają, że struktura wyrażenia lambda jest przejrzysta i wyraźna.

Składnia

  • Wyrażenie <TDelegate> name = lambdaExpression;

Parametry

Parametr Detale
TDelegate Typ delegata, który ma zostać użyty do wyrażenia
lambdaExpression Wyrażenie lambda (np. num => num < 5 )

Uwagi

Wprowadzenie do drzew wyrażeń

Skąd pochodzimy

W drzewach wyrażeń chodzi o zużycie „kodu źródłowego” w czasie wykonywania. Rozważ metodę, która oblicza podatek należny od decimal CalculateTotalTaxDue(SalesOrder order) zamówienia sprzedaży decimal CalculateTotalTaxDue(SalesOrder order) . Korzystanie z tej metody w programie .NET jest łatwe - wystarczy nazwać ją decimal taxDue = CalculateTotalTaxDue(order); . Co jeśli chcesz zastosować go do wszystkich wyników ze zdalnego zapytania (SQL, XML, zdalny serwer itp.)? Te zdalne źródła zapytań nie mogą wywoływać metody! Tradycyjnie we wszystkich tych przypadkach trzeba odwrócić przepływ. Wykonaj całe zapytanie, zapisz je w pamięci, a następnie przejrzyj wyniki i oblicz podatek dla każdego wyniku.

Jak uniknąć problemów z pamięcią i opóźnieniami inwersji przepływu

Drzewa wyrażeń to struktury danych w formacie drzewa, w których każdy węzeł zawiera wyrażenie. Służą do tłumaczenia skompilowanych instrukcji (takich jak metody stosowane do filtrowania danych) w wyrażeniach, które mogłyby być używane poza środowiskiem programu, np. W zapytaniu do bazy danych.

Problem polega na tym, że zdalne zapytanie nie może uzyskać dostępu do naszej metody . Moglibyśmy uniknąć tego problemu, gdyby zamiast tego wysłaliśmy instrukcje dotyczące metody do zdalnego zapytania. W naszym przykładzie CalculateTotalTaxDue oznacza to, że wysyłamy te informacje:

  1. Utwórz zmienną do przechowywania całkowitego podatku
  2. Pętlę przez wszystkie linie w zamówieniu
  3. Dla każdej linii sprawdź, czy produkt podlega opodatkowaniu
  4. Jeśli tak, pomnóż sumę wiersza przez obowiązującą stawkę podatkową i dodaj tę kwotę do sumy
  5. W przeciwnym razie nic nie rób

Dzięki tym instrukcjom zdalne zapytanie może wykonać pracę podczas tworzenia danych.

Istnieją dwa wyzwania związane z wdrożeniem tego. Jak przekształcić skompilowaną metodę .NET w listę instrukcji i jak sformatować instrukcje w taki sposób, aby mogły zostać wykorzystane przez system zdalny?

Bez drzew wyrażeń można rozwiązać tylko pierwszy problem z MSIL. (MSIL to kod podobny do asemblera stworzony przez kompilator .NET.) Analiza MSIL jest możliwa , ale nie jest łatwa. Nawet jeśli poprawnie go przeanalizujesz, może być trudno ustalić, jakie były intencje oryginalnego programisty w odniesieniu do określonej procedury.

Drzewa ekspresji ratują dzień

Drzewa wyrażeń rozwiązują dokładnie te problemy. Reprezentują one instrukcje programu, strukturę drzewa, w której każdy węzeł reprezentuje jedną instrukcję i zawiera odniesienia do wszystkich informacji potrzebnych do wykonania tej instrukcji. Na przykład MethodCallExpression ma odniesienie do 1) metody MethodInfo którą zamierza wywołać, 2) listy Expression , które przekaże tej metodzie, 3) na przykład metod, Expression którym wywoła się metodę. Możesz „przejść się po drzewie” i zastosować instrukcje do zdalnego zapytania.

Tworzenie drzewek ekspresyjnych

Najłatwiejszym sposobem utworzenia drzewa wyrażeń jest wyrażenie lambda. Te wyrażenia wyglądają prawie tak samo jak normalne metody C #. Ważne jest, aby zdać sobie sprawę, że to magia kompilatora . Kiedy po raz pierwszy tworzysz wyrażenie lambda, kompilator sprawdza, do czego go przypisujesz. Jeśli jest to typ Delegate (w tym Action lub Func ), kompilator konwertuje wyrażenie lambda na delegata. Jeśli jest to LambdaExpression (lub Expression<Action<T>> lub Expression<Func<T>> które są silnie typowanymi LambdaExpression ), kompilator przekształca je w LambdaExpression . W tym miejscu zaczyna się magia. Za kulisami kompilator używa interfejsu API drzewa wyrażeń do przekształcenia wyrażenia lambda w wyrażenie LambdaExpression .

Wyrażenia lambda nie mogą tworzyć każdego typu drzewa wyrażeń. W takich przypadkach możesz ręcznie użyć interfejsu API wyrażeń, aby utworzyć potrzebne drzewo. W przykładzie interfejsu API wyrażeń Understanding , tworzymy wyrażenie CalculateTotalSalesTax przy użyciu interfejsu API.

UWAGA: Nazwy są tu nieco mylące. Wyrażenie lambda (dwa słowa, małe litery) odnosi się do bloku kodu ze wskaźnikiem => . Reprezentuje anonimową metodę w C # i jest konwertowany na Delegate lub Expression . LambdaExpression (jedno słowo, PascalCase) odnosi się do typu węzła w Expression API, który reprezentuje metodę, którą można wykonać.

Drzewa ekspresji i LINQ

Jednym z najczęstszych zastosowań drzew wyrażeń jest LINQ i zapytania do baz danych. LINQ łączy drzewo wyrażeń z dostawcą zapytań, aby zastosować instrukcje do docelowego zdalnego zapytania. Na przykład dostawca zapytań LINQ do Entity Framework przekształca drzewo wyrażeń w SQL, które jest wykonywane bezpośrednio na bazie danych.

Łącząc wszystkie elementy, możesz zobaczyć prawdziwą moc LINQ.

  1. Napisz zapytanie, używając wyrażenia lambda: products.Where(x => x.Cost > 5)
  2. Kompilator przekształca to wyrażenie w drzewo wyrażeń za pomocą instrukcji „sprawdź, czy właściwość Cost parametru jest większa niż pięć”.
  3. Dostawca zapytań analizuje drzewo wyrażeń i generuje prawidłowe zapytanie SQL SELECT * FROM products WHERE Cost > 5
  4. ORM wyświetla wszystkie wyniki w POCO i odzyskujesz listę obiektów

Notatki

  • Drzewa ekspresji są niezmienne. Jeśli chcesz zmienić drzewo wyrażeń, musisz utworzyć nowe, skopiować istniejące do nowego (aby przejść przez drzewo wyrażeń, możesz użyć ExpressionVisitor ) i wprowadzić pożądane zmiany.

Tworzenie drzew wyrażeń za pomocą interfejsu 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 });

Kompilowanie drzew wyrażeń

// 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));

Analizowanie drzew wyrażeń

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      

Utwórz drzewa wyrażeń z wyrażeniem lambda

Poniżej znajduje się najbardziej podstawowe drzewo wyrażeń tworzone przez lambda.

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

Aby ręcznie stworzyć drzewa wyrażeń, należy użyć klasy Expression .

Wyrażenie powyżej byłoby równoważne z:

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);

Zrozumienie API wyrażeń

Użyjemy interfejsu API drzewa wyrażeń, aby utworzyć drzewo CalculateSalesTax . Mówiąc wprost, oto podsumowanie kroków, które należy wykonać, aby utworzyć drzewo.

  1. Sprawdź, czy produkt podlega opodatkowaniu
  2. Jeśli tak, pomnóż sumę wiersza przez obowiązującą stawkę podatkową i zwróć tę kwotę
  3. W przeciwnym razie zwróć 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);

Podstawowe drzewo wyrażeń

Drzewa wyrażeń reprezentują kod w drzewiastej strukturze danych, gdzie każdy węzeł jest wyrażeniem

Drzewa wyrażeń umożliwiają dynamiczną modyfikację kodu wykonywalnego, wykonywanie zapytań LINQ w różnych bazach danych oraz tworzenie zapytań dynamicznych. Możesz skompilować i uruchomić kod reprezentowany przez drzewa wyrażeń.

Są one również używane w czasie wykonywania dynamicznego języka (DLR) w celu zapewnienia interoperacyjności między językami dynamicznymi a .NET Framework oraz umożliwienia autorom kompilatorów emitowania drzew wyrażeń zamiast języka pośredniego Microsoft (MSIL).

Drzewa wyrażeń można tworzyć za pośrednictwem

  1. Anonimowe wyrażenie lambda,
  2. Ręcznie, używając przestrzeni nazw System.Linq.Expressions.

Drzewa wyrażeń z wyrażeń lambda

Gdy wyrażenie lambda jest przypisane do zmiennej typu Expression, kompilator emituje kod, aby zbudować drzewo wyrażeń reprezentujące wyrażenie lambda.

Poniższe przykłady kodu pokazują, w jaki sposób kompilator C # może utworzyć drzewo wyrażeń reprezentujące wyrażenie lambda num => num <5.

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

Drzewa wyrażeń za pomocą interfejsu API

Drzewa wyrażeń utworzono również za pomocą klasy wyrażeń . Ta klasa zawiera statyczne metody fabryczne, które tworzą węzły drzewa wyrażeń określonych typów.

Poniżej kilka rodzajów węzłów drzewa.

  1. Wyrażenie parametru
  2. MethodCallExpression

Poniższy przykład kodu pokazuje, jak utworzyć drzewo wyrażeń reprezentujące wyrażenie lambda num => num <5 przy użyciu interfejsu 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 });

Badanie struktury wyrażenia za pomocą narzędzia Visitor

Zdefiniuj nową klasę gości, zastępując niektóre metody 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);
    }
}

Zadzwoń do Visit aby użyć tego użytkownika na istniejącym wyrażeniu:

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow