Sök…


Introduktion

Uttrycksträd är uttryck ordnade i en treliknande datastruktur. Varje nod i trädet är en representation av ett uttryck, ett uttryck är kod. En representation i minnet av ett Lambda-uttryck skulle vara ett uttrycksträd som innehåller de faktiska elementen (dvs. koden) i frågan, men inte dess resultat. Uttrycksträd gör strukturen för ett lambda-uttryck transparent och tydligt.

Syntax

  • Uttryck <Telegat> namn = lambdaExpression;

parametrar

Parameter detaljer
TDelegate Den delegerade typen som ska användas för uttrycket
lambdaExpression Lambda-uttrycket (ex. num => num < 5 )

Anmärkningar

Introduktion till uttrycksträd

Var vi kom ifrån

Uttrycksträd handlar om att konsumera "källkod" vid körning. Överväg en metod som beräknar försäljningsskatten på en försäljningsorder decimal CalculateTotalTaxDue(SalesOrder order) . Att använda den metoden i ett .NET-program är enkelt - du kallar det bara decimal taxDue = CalculateTotalTaxDue(order); . Vad händer om du vill tillämpa det på alla resultat från en fjärrfråga (SQL, XML, en fjärrserver osv)? Dessa fjärrfrågor kan inte kalla metoden! Traditionellt måste du vända flödet i alla dessa fall. Gör hela frågan, lagra den i minnet och gå sedan igenom resultaten och beräkna skatt för varje resultat.

Hur man undviker flödesinversionsminne- och latensproblem

Uttrycksträd är datastrukturer i ett format av ett träd, där varje nod har ett uttryck. De används för att översätta de sammanställda instruktionerna (som metoder som används för att filtrera data) i uttryck som kan användas utanför programmiljön, t.ex. i en databasfråga.

Problemet här är att en fjärrfråga inte kan komma åt vår metod . Vi kan undvika detta problem om vi istället skickade instruktionerna för metoden till fjärrfrågan. I vårt CalculateTotalTaxDue exempel betyder det att vi skickar denna information:

  1. Skapa en variabel för att lagra den totala skatten
  2. Gå igenom alla rader på beställningen
  3. Kontrollera om produkten är beskattningsbar för varje rad
  4. Om så är fallet multiplicerar du linjesumman med tillämplig skattesats och lägger till det beloppet till summan
  5. Annars gör ingenting

Med dessa instruktioner kan fjärrfrågan utföra arbetet eftersom det skapar data.

Det finns två utmaningar att genomföra detta. Hur förvandlar du en kompilerad .NET-metod till en lista med instruktioner och hur formaterar du instruktionerna på ett sätt som de kan konsumeras av fjärrsystemet?

Utan uttrycksträd kunde du bara lösa det första problemet med MSIL. (MSIL är den monteringsliknande koden som skapats av .NET-kompilatorn.) Analysering av MSIL är möjlig , men det är inte lätt. Även när du analyserar det ordentligt kan det vara svårt att avgöra vad den ursprungliga programmerarens avsikt var med en viss rutin.

Uttrycksträd räddar dagen

Uttrycksträd adresserar dessa exakta problem. De representerar programinstruktioner en träddatastruktur där varje nod representerar en instruktion och har referenser till all information du behöver för att utföra denna instruktion. Till exempel har en MethodCallExpression hänvisning till 1) MethodInfo den kommer att ringa, 2) en lista med Expression s som den kommer att gå till den metoden, 3) till exempel metoder, det Expression du kommer att kalla metoden på. Du kan "gå i trädet" och tillämpa instruktionerna på din fjärrfråga.

Skapa uttrycksträd

Det enklaste sättet att skapa ett uttrycksträd är med ett lambda-uttryck. Dessa uttryck ser nästan lika ut som normala C # -metoder. Det är viktigt att inse att detta är kompilatormagi . När du först skapar ett lambda-uttryck kontrollerar kompilatorn vad du tilldelar det. Om det är en Delegate typ (inklusive Action eller Func ), omvandlar kompilatorn lambda-uttrycket till en delegat. Om det är en LambdaExpression (eller ett Expression<Action<T>> eller Expression<Func<T>> som är starkt typade LambdaExpression ), omvandlar kompilatorn den till en LambdaExpression . Det är här som magin sparkar in. Bakom kulisserna använder kompilatorn uttrycksträdets API för att förvandla ditt lambda-uttryck till en LambdaExpression .

Lambda-uttryck kan inte skapa alla typer av uttrycksträd. I dessa fall kan du använda Expressions API manuellt för att skapa det träd du behöver. I exemplet Förstå uttryckens API skapar vi CalculateTotalSalesTax CalculTotalSalesTax med API: et.

OBS: Namnen blir lite förvirrande här. Ett lambda-uttryck (två ord, små bokstäver) hänvisar till kodblocket med en => indikator. Det representerar en anonym metod i C # och omvandlas till antingen en Delegate eller ett Expression . En LambdaExpression (ett ord, PascalCase) hänvisar till nodtypen i Expression API som representerar en metod som du kan utföra.

Uttrycksträd och LINQ

En av de vanligaste användningarna av uttrycksträd är med LINQ och databasfrågor. LINQ parar ett uttrycksträd med en frågeföretag för att tillämpa dina instruktioner på målfjärrfrågan. Till exempel förvandlar frågeföretaget LINQ to Entity Framework ett uttrycksträd till SQL som körs direkt mot databasen.

Om du sätter ihop alla bitarna kan du se den verkliga kraften bakom LINQ.

  1. Skriv en fråga med ett lambda-uttryck: products.Where(x => x.Cost > 5)
  2. Kompilatorn förvandlar det uttrycket till ett uttrycksträd med instruktionerna "kontrollera om parametern kostnadsegenskaper är större än fem".
  3. Frågeföretaget analyserar uttrycksträdet och producerar en giltig SQL-fråga SELECT * FROM products WHERE Cost > 5
  4. ORM projicerar alla resultat i POCO: er och du får en lista med objekt tillbaka

anteckningar

  • Uttrycksträd är oföränderliga. Om du vill ändra ett uttrycksträd måste du skapa ett nytt, kopiera det befintliga till det nya (för att korsa ett uttrycksträd kan du använda ExpressionVisitor ) och göra önskade ändringar.

Skapa uttrycksträd med API: et

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

Kompilera uttrycksträd

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

Analysera uttrycksträd

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      

Skapa uttrycksträd med ett lambda-uttryck

Följande är det mest grundläggande uttrycksträdet som skapas av lambda.

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

För att skapa uttrycksträd "för hand" bör man använda Expression klassen.

Uttrycket ovan skulle motsvara:

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

Förstå uttryckens API

Vi kommer att använda uttrycket träd API för att skapa ett CalculateSalesTax träd. På vanligt engelska, här är en sammanfattning av stegen som det tar för att skapa trädet.

  1. Kontrollera om produkten är beskattningsbar
  2. Om så är fallet multiplicerar du linjen totalt med den tillämpliga skattesatsen och returnerar det beloppet
  3. I annat fall returnera 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);

Expression Tree Basic

Uttrycksträd representerar kod i en trädliknande datastruktur, där varje nod är ett uttryck

Expression Trees möjliggör dynamisk modifiering av körbar kod, exekvering av LINQ-frågor i olika databaser och skapande av dynamiska frågor. Du kan sammanställa och köra kod representerad av uttrycksträd.

Dessa används också i den dynamiska språktiden (DLR) för att tillhandahålla interoperabilitet mellan dynamiska språk och .NET Framework och för att göra det möjligt för kompilatorförfattare att avge uttrycksträd istället för Microsofts mellanspråk (MSIL).

Expressionsträd kan skapas via

  1. Anonymt lambda-uttryck,
  2. Manuellt genom att använda System.Linq.Expressions namnutrymme.

Uttrycksträd från Lambda-uttryck

När ett lambda-uttryck tilldelas variablen Expression-typ avger kompilatorn kod för att bygga ett uttrycksträd som representerar lambda-uttrycket.

Följande kodexempel visar hur C # -kompileraren ska skapa ett uttrycksträd som representerar lambda-uttrycket num => num <5.

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

Uttrycksträd med API: et

Expressionsträd skapade också med hjälp av Expression Class. Den här klassen innehåller statiska fabriksmetoder som skapar uttrycksträdnoder av specifika typer.

Nedan finns få typer av trädnoder.

  1. ParameterExpression
  2. MethodCallExpression

Följande kodexempel visar hur man skapar ett uttrycksträd som representerar lambda-uttrycket num => num <5 med hjälp av 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 });

Undersöka strukturen för ett uttryck med hjälp av besökare

Definiera en ny besökarklass genom att åsidosätta några av metoderna för 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);
    }
}

Ring Visit att använda den här besökaren på ett befintligt uttryck:

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow