C# Language
Alberi di espressione
Ricerca…
introduzione
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:
- Creare una variabile per memorizzare l'imposta totale
- Passa attraverso tutte le linee sull'ordine
- Per ogni riga, controlla se il prodotto è tassabile
- Se lo è, moltiplica la linea totale per l'aliquota fiscale applicabile e aggiungi tale importo al totale
- 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, unMethodCallExpression
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 diDelegate
(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.
- Scrivi una query usando un'espressione lambda:
products.Where(x => x.Cost > 5)
- Il compilatore trasforma quell'espressione in un albero di espressioni con le istruzioni "controlla se la proprietà Cost del parametro è maggiore di cinque".
- Il provider di query analizza la struttura dell'espressione e produce una query SQL valida
SELECT * FROM products WHERE Cost > 5
- 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.
- Controlla se il prodotto è tassabile
- Se lo è, moltiplica la linea totale per l'aliquota fiscale applicabile e restituisci tale importo
- 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
- Espressione lambda anonima,
- 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.
- ParameterExpression
- 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);