C# Language
Arbres d'expression
Recherche…
Introduction
Syntaxe
- Expression <TDelegate> name = lambdaExpression;
Paramètres
Paramètre | Détails |
---|---|
Délégué TD | Le type de délégué à utiliser pour l'expression |
lambdaExpression | L'expression lambda (ex. num => num < 5 ) |
Remarques
Introduction aux arbres d'expression
D'où nous venons
Les arborescences d'expression sont toutes axées sur la consommation de "code source" au moment de l'exécution. Envisagez une méthode qui calcule la taxe de vente due sur une decimal CalculateTotalTaxDue(SalesOrder order)
. Utiliser cette méthode dans un programme .NET est facile - vous appelez simplement decimal taxDue = CalculateTotalTaxDue(order);
. Que faire si vous souhaitez l'appliquer à tous les résultats d'une requête distante (SQL, XML, un serveur distant, etc.)? Ces sources de requêtes distantes ne peuvent pas appeler la méthode! Traditionnellement, vous devez inverser le flux dans tous ces cas. Effectuez l'intégralité de la requête, stockez-la en mémoire, puis parcourez les résultats et calculez la taxe pour chaque résultat.
Comment éviter les problèmes de mémoire et de latence de l'inversion de flux
Les arbres d'expression sont des structures de données dans un format d'arbre, où chaque noeud contient une expression. Ils sont utilisés pour traduire les instructions compilées (comme les méthodes utilisées pour filtrer les données) dans des expressions qui pourraient être utilisées en dehors de l'environnement du programme, par exemple dans une requête de base de données.
Le problème ici est qu'une requête distante ne peut pas accéder à notre méthode . Nous pourrions éviter ce problème si, à la place, nous avons envoyé les instructions pour la méthode à la requête distante. Dans notre exemple CalculateTotalTaxDue
, cela signifie que nous envoyons ces informations:
- Créer une variable pour stocker la taxe totale
- Traverser toutes les lignes de la commande
- Pour chaque ligne, vérifiez si le produit est taxable
- Si c'est le cas, multipliez le total de la ligne par le taux de taxe applicable et ajoutez ce montant au total.
- Sinon ne rien faire
Avec ces instructions, la requête distante peut exécuter le travail lors de la création des données.
Il y a deux défis à relever pour la mettre en œuvre. Comment transformer une méthode .NET compilée en une liste d'instructions et comment formater les instructions de manière à ce qu'elles puissent être utilisées par le système distant?
Sans arbres d'expression, vous ne pouvez résoudre le premier problème avec MSIL. (MSIL est le code de type assembleur créé par le compilateur .NET.) L'analyse MSIL est possible , mais ce n'est pas facile. Même si vous analysez correctement le contenu, il peut être difficile de déterminer quelle était l'intention du programmeur d'origine avec une routine particulière.
Les arbres d'expression sauvent la journée
Les arbres d'expression traitent ces problèmes exacts. Ils représentent des instructions de programme dans une structure de données arborescente où chaque nœud représente une instruction et contient des références à toutes les informations dont vous avez besoin pour exécuter cette instruction. Par exemple, un objetMethodCallExpression
fait référence à 1) le MethodInfo
qu'il va appeler, 2) une liste d' Expression
qu'il passera à cette méthode, 3) pour les méthodes d'instance, l' Expression
que vous appellerez la méthode. Vous pouvez "parcourir l'arborescence" et appliquer les instructions sur votre requête distante. Créer des arbres d'expression
Le moyen le plus simple de créer un arbre d'expression est d'utiliser une expression lambda. Ces expressions sont presque identiques aux méthodes C # normales. Il est important de réaliser que c'est la magie du compilateur . Lorsque vous créez une expression lambda pour la première fois, le compilateur vérifie ce que vous lui attribuez. S'il s'agit d'un typeDelegate
(y compris Action
ou Func
), le compilateur convertit l'expression lambda en un délégué. Si c'est un LambdaExpression
(ou une Expression<Action<T>>
ou Expression<Func<T>>
qui est fortement typé LambdaExpression
), le compilateur le transforme en LambdaExpression
. C'est là LambdaExpression
la magie. En arrière-plan, le compilateur utilise l'API API pour transformer votre expression lambda en une expression LambdaExpression
. Les expressions lambda ne peuvent pas créer chaque type d'arbre d'expression. Dans ces cas, vous pouvez utiliser l'API Expressions manuellement pour créer l'arborescence requise. Dans l'exemple de l' API Comprendre les expressions , nous créons l'expression CalculateTotalSalesTax
à l'aide de l'API.
NOTE: Les noms sont un peu confus ici. Une expression lambda (deux mots, minuscule) fait référence au bloc de code avec un indicateur =>
. Il représente une méthode anonyme en C # et est converti en Delegate
ou Expression
. Un LambdaExpression
(un mot, PascalCase) fait référence au type de noeud dans l'API Expression qui représente une méthode que vous pouvez exécuter.
Arbres d'expression et LINQ
L'une des utilisations les plus courantes des arborescences d'expression concerne les requêtes LINQ et de base de données. LINQ associe une arborescence d'expression à un fournisseur de requêtes pour appliquer vos instructions à la requête distante cible. Par exemple, le fournisseur de requêtes LINQ to Entity Framework transforme un arbre d'expression en SQL qui est exécuté directement sur la base de données.
En rassemblant toutes les pièces, vous pouvez voir le véritable pouvoir derrière LINQ.
- Ecrivez une requête en utilisant une expression lambda:
products.Where(x => x.Cost > 5)
- Le compilateur transforme cette expression en une arborescence d'expression avec les instructions "vérifier si la propriété Cost du paramètre est supérieure à cinq".
- Le fournisseur de requêtes analyse l'arborescence des expressions et génère une requête SQL valide
SELECT * FROM products WHERE Cost > 5
- L'ORM projette tous les résultats dans des POCO et vous obtenez une liste d'objets
Remarques
- Les arbres d'expression sont immuables. Si vous souhaitez modifier une arborescence d'expression pour en créer une nouvelle, copiez celle existante dans la nouvelle (pour parcourir une arborescence d'expression que vous pouvez utiliser avec
ExpressionVisitor
) et apportez les modifications souhaitées.
Création d'arbres d'expression à l'aide de 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 });
Compilation d'arbres d'expression
// 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));
Analyse d'arbres d'expression
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
Créer des arbres d'expression avec une expression lambda
Voici l'arbre d'expression le plus fondamental créé par lambda.
Expression<Func<int, bool>> lambda = num => num == 42;
Pour créer des arbres d'expression "à la main", il faut utiliser la classe Expression
.
L'expression ci-dessus serait équivalente à:
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);
Comprendre l'API des expressions
Nous allons utiliser l'API API pour créer un arbre CalculateSalesTax
. En clair, voici un résumé des étapes nécessaires à la création de l'arborescence.
- Vérifiez si le produit est taxable
- Si c'est le cas, multipliez le total de la ligne par le taux de taxe applicable et retournez ce montant
- Sinon retourne 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);
Arbre d'expression de base
Les arbres d'expression représentent du code dans une structure de données arborescente, où chaque nœud est une expression
Expression Trees permet la modification dynamique du code exécutable, l'exécution de requêtes LINQ dans diverses bases de données et la création de requêtes dynamiques. Vous pouvez compiler et exécuter du code représenté par des arbres d'expression.
Celles-ci sont également utilisées dans le DLR (Dynamic Language Runtime) pour assurer l'interopérabilité entre les langages dynamiques et le .NET Framework et pour permettre aux auteurs du compilateur d'émettre des arborescences d'expression au lieu du langage intermédiaire Microsoft (MSIL).
Des arbres d'expression peuvent être créés via
- Expression lambda anonyme,
- Manuellement en utilisant l'espace de noms System.Linq.Expressions.
Arbres d'expression des expressions lambda
Lorsqu'une expression lambda est affectée à une variable de type Expression, le compilateur émet du code pour générer une arborescence d'expression qui représente l'expression lambda.
Les exemples de code suivants montrent comment faire en sorte que le compilateur C # crée un arbre d'expression qui représente l'expression lambda num => num <5.
Expression<Func<int, bool>> lambda = num => num < 5;
Arbres d'expression à l'aide de l'API
Les arbres d'expression ont également été créés à l'aide de la classe d' expression . Cette classe contient des méthodes de fabrique statiques qui créent des nœuds d'arbre d'expression de types spécifiques.
Vous trouverez ci-dessous quelques types de nœuds d'arbre.
- ParameterExpression
- MethodCallExpression
L'exemple de code suivant montre comment créer une arborescence d'expression qui représente l'expression lambda num => num <5 en utilisant 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 });
Examen de la structure d'une expression à l'aide de Visitor
Définissez une nouvelle classe de visiteur en remplaçant certaines des méthodes d' 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);
}
}
Appelez Visit
pour utiliser ce visiteur sur une expression existante:
Expression<Func<int,bool>> isBig = a => a > 1000000;
var visitor = new PrintingVisitor();
visitor.Visit(isBig);