Ricerca…


introduzione

Gli alberi di espressione sono espressioni disposte in una struttura di dati simile a un albero. Ogni nodo nell'albero è una rappresentazione di un'espressione, un'espressione essendo un codice. Una rappresentazione in memoria di un'espressione Lambda sarebbe un albero di espressione, che contiene gli elementi effettivi (cioè il codice) della query, ma non il suo risultato. Gli alberi di espressione rendono la struttura di un'espressione lambda trasparente ed esplicita.

Sintassi

  • Espressione <TDelegate> name = lambdaExpression;

Parametri

Parametro Dettagli
TDelegate Il tipo di delegato da utilizzare per l'espressione
lambdaExpression L'espressione lambda (es. num => num < 5 )

Osservazioni

Introduzione agli alberi delle espressioni

Da dove veniamo

Gli alberi delle espressioni si occupano solo del consumo di "codice sorgente" in fase di esecuzione. Considerare un metodo che calcola l'imposta sulle vendite dovuta su un decimal CalculateTotalTaxDue(SalesOrder order) dell'ordine di vendita decimal CalculateTotalTaxDue(SalesOrder order) . L'utilizzo di tale metodo in un programma .NET è semplice: basta chiamarlo decimal taxDue = CalculateTotalTaxDue(order); . Cosa succede se si desidera applicarlo a tutti i risultati di una query remota (SQL, XML, un server remoto, ecc.)? Quelle fonti di query remote non possono chiamare il metodo! Tradizionalmente, dovresti invertire il flusso in tutti questi casi. Crea l'intera query, memorizzala in memoria, quindi visualizza i risultati e calcola le imposte per ogni risultato.

Come evitare i problemi di memoria e latenza di inversione del flusso

Gli alberi di espressione sono strutture di dati in un formato di un albero, in cui ogni nodo contiene un'espressione. Sono utilizzati per tradurre le istruzioni compilate (come i metodi utilizzati per filtrare i dati) in espressioni che potrebbero essere utilizzate al di fuori dell'ambiente del programma, come all'interno di una query del database.

Il problema qui è che una query remota non può accedere al nostro metodo . Potremmo evitare questo problema se, invece, abbiamo inviato le istruzioni per il metodo alla query remota. Nel nostro esempio CalculateTotalTaxDue , ciò significa che inviamo queste informazioni:

  1. Creare una variabile per memorizzare l'imposta totale
  2. Passa attraverso tutte le linee sull'ordine
  3. Per ogni riga, controlla se il prodotto è tassabile
  4. Se lo è, moltiplica la linea totale per l'aliquota fiscale applicabile e aggiungi tale importo al totale
  5. Altrimenti non fare nulla

Con queste istruzioni, la query remota può eseguire il lavoro mentre crea i dati.

Ci sono due sfide per l'implementazione di questo. Come si trasforma un metodo .NET compilato in un elenco di istruzioni e come si formattano le istruzioni in modo che possano essere utilizzate dal sistema remoto?

Senza alberi di espressione, è possibile risolvere solo il primo problema con MSIL. (MSIL è il codice simile all'assembler creato dal compilatore .NET). L'analisi di MSIL è possibile , ma non è facile. Anche quando lo si analizza correttamente, può essere difficile determinare quale fosse l'intento del programmatore originale con una particolare routine.

Gli alberi delle espressioni salvano il giorno

Gli alberi delle espressioni affrontano questi problemi esatti. Rappresentano le istruzioni del programma una struttura di dati dell'albero in cui ogni nodo rappresenta un'istruzione e contiene riferimenti a tutte le informazioni necessarie per eseguire tale istruzione. Ad esempio, un MethodCallExpression ha riferimento a 1) il MethodInfo che chiamerà, 2) un elenco di Expression che passerà a quel metodo, 3) per i metodi di istanza, l' Expression cui chiamerai il metodo. Puoi "camminare sull'albero" e applicare le istruzioni sulla tua query remota.

Creazione di alberi di espressione

Il modo più semplice per creare un albero di espressioni è con un'espressione lambda. Queste espressioni sembrano quasi le stesse dei normali metodi C #. È importante rendersi conto che questo è magia del compilatore . Quando crei per la prima volta un'espressione lambda, il compilatore verifica a cosa lo assegni. Se si tratta di un tipo di Delegate (incluso Action o Func ), il compilatore converte l'espressione lambda in un delegato. Se è una LambdaExpression (o Expression<Action<T>> o Expression<Func<T>> che sono fortemente digitate LambdaExpression ), il compilatore la trasforma in LambdaExpression . Qui è dove entra la magia. Dietro le quinte, il compilatore usa l'albero delle espressioni API per trasformare la tua espressione lambda in una LambdaExpression .

Le espressioni Lambda non possono creare ogni tipo di albero delle espressioni. In questi casi, è possibile utilizzare manualmente l'API di espressioni per creare l'albero necessario. Nell'esempio Comprendere le espressioni API , creiamo l'espressione CalculateTotalSalesTax utilizzando l'API.

NOTA: i nomi diventano un po 'confusi qui. Un'espressione lambda (due parole, in minuscolo) si riferisce al blocco di codice con un indicatore => . Rappresenta un metodo anonimo in C # e viene convertito in un Delegate o in Expression . A LambdaExpression (una parola, PascalCase) fa riferimento al tipo di nodo all'interno dell'API di espressione che rappresenta un metodo che è possibile eseguire.

Alberi di espressione e LINQ

Uno degli usi più comuni degli alberi di espressione è con LINQ e query di database. LINQ accoppia un albero di espressioni con un provider di query per applicare le istruzioni alla query remota di destinazione. Ad esempio, il provider di query LINQ to Entity Framework trasforma un albero di espressioni in SQL che viene eseguito direttamente sul database.

Mettendo insieme tutti i pezzi, puoi vedere il vero potere dietro LINQ.

  1. Scrivi una query usando un'espressione lambda: products.Where(x => x.Cost > 5)
  2. Il compilatore trasforma quell'espressione in un albero di espressioni con le istruzioni "controlla se la proprietà Cost del parametro è maggiore di cinque".
  3. Il provider di query analizza la struttura dell'espressione e produce una query SQL valida SELECT * FROM products WHERE Cost > 5
  4. L'ORM proietta tutti i risultati in POCO e ottieni un elenco di oggetti

Gli appunti

  • Gli alberi delle espressioni sono immutabili. Se si desidera modificare un albero di espressioni è necessario crearne uno nuovo, copiare quello esistente in quello nuovo (per attraversare un albero di espressioni è possibile utilizzare ExpressionVisitor ) e apportare le modifiche desiderate.

Creazione di alberi di espressione mediante l'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 });

Compilazione degli alberi di espressione

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

Parsing Expression Trees

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      

Crea alberi espressione con un'espressione lambda

Di seguito è riportato l'albero delle espressioni di base creato da lambda.

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

Per creare alberi di espressioni 'a mano', si dovrebbe usare la classe Expression .

L'espressione sopra sarebbe equivalente a:

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

Comprendere l'API delle espressioni

Utilizzeremo l'albero delle espressioni API per creare un albero CalculateSalesTax . In inglese semplice, ecco un riepilogo dei passaggi necessari per creare l'albero.

  1. Controlla se il prodotto è tassabile
  2. Se lo è, moltiplica la linea totale per l'aliquota fiscale applicabile e restituisci tale importo
  3. Altrimenti restituisci 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

Gli alberi di espressione rappresentano il codice in una struttura dati ad albero, in cui ogni nodo è un'espressione

Expression Trees consente la modifica dinamica del codice eseguibile, l'esecuzione di query LINQ in vari database e la creazione di query dinamiche. È possibile compilare ed eseguire codice rappresentato da alberi di espressione.

Questi vengono anche utilizzati nel linguaggio dinamico run-time (DLR) per fornire l'interoperabilità tra i linguaggi dinamici e .NET Framework e per consentire ai produttori di compilatori di emettere alberi di espressioni anziché il linguaggio intermedio Microsoft (MSIL).

Gli alberi di espressione possono essere creati tramite

  1. Espressione lambda anonima,
  2. Manualmente utilizzando lo spazio dei nomi System.Linq.Expressions.

Expression Trees from Lambda Expressions

Quando un'espressione lambda viene assegnata alla variabile di tipo Expression, il compilatore emette il codice per creare un albero di espressioni che rappresenti l'espressione lambda.

I seguenti esempi di codice mostrano come fare in modo che il compilatore C # crei un albero di espressioni che rappresenti l'espressione lambda num => num <5.

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

Alberi di espressione utilizzando l'API

Alberi di espressione creati anche usando la classe di espressione . Questa classe contiene metodi factory statici che creano nodi di albero dell'espressione di tipi specifici.

Di seguito sono riportati alcuni tipi di nodi Tree.

  1. ParameterExpression
  2. MethodCallExpression

Il seguente esempio di codice mostra come creare un albero di espressioni che rappresenti l'espressione lambda num => num <5 utilizzando l'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 });

Esaminando la struttura di un'espressione usando Visitor

Definisci una nuova classe di visitatori sovrascrivendo alcuni dei metodi di 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);
    }
}

Chiama Visit per utilizzare questo visitatore su un'espressione esistente:

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow