Recherche…


Introduction

Les arbres d'expression sont des expressions organisées dans une structure de données arborescente. Chaque nœud de l'arborescence est une représentation d'une expression, une expression étant du code. Une représentation en mémoire d'une expression Lambda serait un arbre d'expression qui contiendrait les éléments réels (c'est-à-dire le code) de la requête, mais pas son résultat. Les arbres d'expression rendent la structure d'une expression lambda transparente et explicite.

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:

  1. Créer une variable pour stocker la taxe totale
  2. Traverser toutes les lignes de la commande
  3. Pour chaque ligne, vérifiez si le produit est taxable
  4. Si c'est le cas, multipliez le total de la ligne par le taux de taxe applicable et ajoutez ce montant au total.
  5. 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 objet MethodCallExpression 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 type Delegate (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.

  1. Ecrivez une requête en utilisant une expression lambda: products.Where(x => x.Cost > 5)
  2. 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".
  3. 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
  4. 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.

  1. Vérifiez si le produit est taxable
  2. Si c'est le cas, multipliez le total de la ligne par le taux de taxe applicable et retournez ce montant
  3. 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

  1. Expression lambda anonyme,
  2. 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.

  1. ParameterExpression
  2. 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);


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow