Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Il compilatore C# ha creato tutti gli alberi delle espressioni finora visualizzati. È stata creata un'espressione lambda assegnata a una variabile tipizzata come Expression<Func<T>> o un tipo simile. Per molti scenari, si compila un'espressione in memoria in fase di esecuzione.
Gli alberi delle espressioni non sono modificabili. Essere immutabile significa che è necessario costruire l'albero dalle radici alle foglie. Le API usate per costruire alberi di espressioni riflettono questo fatto: i metodi usati per costruire un nodo accettano tutti i relativi elementi figli come argomenti. Verranno ora illustrati alcuni esempi per illustrare le tecniche.
Creare nodi
Si inizia con l'espressione di addizione usata in queste sezioni:
Expression<Func<int>> sum = () => 1 + 2;
Per costruire l'albero delle espressioni, si costruiscono innanzitutto i nodi foglia. I nodi foglia sono costanti. Usare il Constant metodo per creare i nodi:
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
Compilare quindi l'espressione di addizione:
var addition = Expression.Add(one, two);
Dopo aver compilato l'espressione di addizione, si crea l'espressione lambda:
var lambda = Expression.Lambda(addition);
Questa espressione lambda non contiene argomenti. Più avanti in questa sezione viene illustrato come eseguire il mapping degli argomenti ai parametri e creare espressioni più complesse.
Per le espressioni come questa, è possibile combinare tutte le chiamate in una singola istruzione:
var lambda2 = Expression.Lambda(
Expression.Add(
Expression.Constant(1, typeof(int)),
Expression.Constant(2, typeof(int))
)
);
Creare un albero
Nella sezione precedente sono stati illustrati i concetti di base per la creazione di un albero delle espressioni in memoria. Gli alberi più complessi in genere indicano più tipi di nodo e più nodi nell'albero. Si esaminerà un altro esempio e verranno visualizzati altri due tipi di nodo compilati in genere quando si creano alberi delle espressioni: i nodi dell'argomento e i nodi di chiamata al metodo. Si creerà un albero delle espressioni per creare questa espressione:
Expression<Func<double, double, double>> distanceCalc =
(x, y) => Math.Sqrt(x * x + y * y);
Per iniziare, creare espressioni di parametro per x e y:
var xParameter = Expression.Parameter(typeof(double), "x");
var yParameter = Expression.Parameter(typeof(double), "y");
La creazione delle espressioni di moltiplicazione e addizione segue il modello già visto:
var xSquared = Expression.Multiply(xParameter, xParameter);
var ySquared = Expression.Multiply(yParameter, yParameter);
var sum = Expression.Add(xSquared, ySquared);
Successivamente, è necessario creare un'espressione di chiamata al metodo per la chiamata a Math.Sqrt.
var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) }) ?? throw new InvalidOperationException("Math.Sqrt not found!");
var distance = Expression.Call(sqrtMethod, sum);
La GetMethod chiamata potrebbe restituire null se il metodo non viene trovato. Molto probabilmente questo è dovuto al fatto che il nome del metodo è stato digitato in modo non ortografico. In caso contrario, potrebbe significare che l'assembly richiesto non è stato caricato. Infine, inserire la chiamata al metodo in un'espressione lambda e assicurarsi di definire gli argomenti per l'espressione lambda:
var distanceLambda = Expression.Lambda(
distance,
xParameter,
yParameter);
In questo esempio più complesso si noteranno un paio di tecniche in più che spesso sono necessarie per creare alberi delle espressioni.
Prima di usarli, è necessario creare gli oggetti che rappresentano parametri o variabili locali. Dopo aver creato questi oggetti, è possibile usarli nell'albero delle espressioni ovunque sia necessario.
In secondo luogo, è necessario usare un subset delle API Reflection per creare un System.Reflection.MethodInfo oggetto in modo da poter creare un albero delle espressioni per accedere a tale metodo. È necessario limitarsi al subset delle API Reflection disponibili nella piattaforma .NET Core. Anche in questo caso, queste tecniche si estendono ad altri alberi delle espressioni.
Creare codice in modo approfondito
Non si è limitati a ciò che è possibile compilare usando queste API. Tuttavia, più complesso è l'albero delle espressioni che si vuole costruire, più è difficile gestire e leggere il codice.
Si creerà ora un albero delle espressioni equivalente a questo codice:
Func<int, int> factorialFunc = (n) =>
{
var res = 1;
while (n > 1)
{
res = res * n;
n--;
}
return res;
};
Il codice precedente non ha costruito l'albero delle espressioni, ma semplicemente il delegato. Con la classe Expression non è possibile costruire lambda di dichiarazione. Ecco il codice necessario per compilare la stessa funzionalità. Non esiste un'API per la compilazione di un while ciclo, ma è necessario creare un ciclo contenente un test condizionale e una destinazione di etichetta per interrompere il ciclo.
var nArgument = Expression.Parameter(typeof(int), "n");
var result = Expression.Variable(typeof(int), "result");
// Creating a label that represents the return value
LabelTarget label = Expression.Label(typeof(int));
var initializeResult = Expression.Assign(result, Expression.Constant(1));
// This is the inner block that performs the multiplication,
// and decrements the value of 'n'
var block = Expression.Block(
Expression.Assign(result,
Expression.Multiply(result, nArgument)),
Expression.PostDecrementAssign(nArgument)
);
// Creating a method body.
BlockExpression body = Expression.Block(
new[] { result },
initializeResult,
Expression.Loop(
Expression.IfThenElse(
Expression.GreaterThan(nArgument, Expression.Constant(1)),
block,
Expression.Break(label, result)
),
label
)
);
Il codice per compilare l'albero delle espressioni per la funzione fattoriale è un po' più lungo, più complicato e contiene etichette e istruzioni di interruzione e altri elementi da evitare nelle attività di codifica quotidiane.
Per questa sezione è stato scritto codice per visitare ogni nodo in questo albero delle espressioni e scrivere informazioni sui nodi creati in questo esempio. È possibile visualizzare o scaricare il codice di esempio nel repository GitHub dotnet/docs. Sperimenta tu stesso creando ed eseguendo i campioni.
Eseguire il mapping dei costrutti di codice alle espressioni
L'esempio di codice seguente illustra un albero delle espressioni che rappresenta l'espressione num => num < 5 lambda usando l'API.
// 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 });
L'API degli alberi delle espressioni supporta anche le assegnazioni e le espressioni del flusso di controllo, ad esempio cicli, blocchi condizionali e try-catch blocchi. Usando l'API, è possibile creare alberi delle espressioni più complessi di quelli che possono essere creati da espressioni lambda dal compilatore C#. Nell'esempio seguente viene illustrato come creare un albero delle espressioni che calcola il fattoriale di un numero.
// Creating a parameter expression.
ParameterExpression value = Expression.Parameter(typeof(int), "value");
// Creating an expression to hold a local variable.
ParameterExpression result = Expression.Parameter(typeof(int), "result");
// Creating a label to jump to from a loop.
LabelTarget label = Expression.Label(typeof(int));
// Creating a method body.
BlockExpression block = Expression.Block(
// Adding a local variable.
new[] { result },
// Assigning a constant to a local variable: result = 1
Expression.Assign(result, Expression.Constant(1)),
// Adding a loop.
Expression.Loop(
// Adding a conditional block into the loop.
Expression.IfThenElse(
// Condition: value > 1
Expression.GreaterThan(value, Expression.Constant(1)),
// If true: result *= value --
Expression.MultiplyAssign(result,
Expression.PostDecrementAssign(value)),
// If false, exit the loop and go to the label.
Expression.Break(label, result)
),
// Label to jump to.
label
)
);
// Compile and execute an expression tree.
int factorial = Expression.Lambda<Func<int, int>>(block, value).Compile()(5);
Console.WriteLine(factorial);
// Prints 120.
Per altre informazioni, vedere Generazione di metodi dinamici con alberi delle espressioni in Visual Studio 2010, che si applica anche alle versioni successive di Visual Studio.