c# func - Des arbres d'expression pour les nuls?




system linq (7)

Je suis le mannequin dans ce scénario.

J'ai essayé de lire sur Google ce que c'est mais je ne comprends pas. Est-ce que quelqu'un peut me donner une explication simple de ce qu'ils sont et pourquoi ils sont utiles?

edit: Je parle de la fonctionnalité LINQ dans .Net.


Answers

La meilleure explication sur les arbres d'expression que j'ai jamais lu est cet article de Charlie Calvert.

Résumer;

Un arbre d'expression représente ce que vous voulez faire, pas comment vous voulez le faire.

Considérez l'expression lambda très simple suivante:
Func<int, int, int> function = (a, b) => a + b;

Cette déclaration comprend trois sections:

  • Une déclaration: fonction Func<int, int, int> function
  • Un opérateur égal: =
  • Une expression lambda: (a, b) => a + b;

La function variable pointe sur un code exécutable brut qui sait comment ajouter deux nombres .

C'est la différence la plus importante entre les délégués et les expressions. Vous appelez function (un Func<int, int, int> ) sans jamais savoir ce qu’il va faire des deux entiers que vous avez passés. Il en faut deux et retourne un, c’est tout ce que votre code peut savoir.

Dans la section précédente, vous avez vu comment déclarer une variable pointant sur du code exécutable brut. Les arbres d'expression ne sont pas du code exécutable , ils sont une forme de structure de données.

Maintenant, contrairement aux délégués, votre code peut savoir ce qu’un arbre d’expression est censé faire.

LINQ fournit une syntaxe simple pour traduire le code en une structure de données appelée arbre d'expression. La première étape consiste à ajouter une instruction using afin d'introduire l'espace de noms Linq.Expressions :

using System.Linq.Expressions;

Maintenant, nous pouvons créer un arbre d'expression:
Expression<Func<int, int, int>> expression = (a, b) => a + b;

L'expression lambda identique présentée dans l'exemple précédent est convertie en un arbre d'expression déclaré comme étant de type Expression<T> . L' expression identifiant n'est pas un code exécutable; c'est une structure de données appelée un arbre d'expression.

Cela signifie que vous ne pouvez pas simplement invoquer un arbre d’expression comme vous pourriez invoquer un délégué, mais vous pouvez l’analyser. Alors, que peut comprendre votre code en analysant l’ expression variable?

// `expression.NodeType` returns NodeType.Lambda.
// `expression.Type` returns Func<int, int, int>.
// `expression.ReturnType` returns Int32.

var body = expression.Body;
// `body.NodeType` returns ExpressionType.Add.
// `body.Type` returns System.Int32.

var parameters = expression.Parameters;
// `parameters.Count` returns 2.

var firstParam = parameters[0];
// `firstParam.Name` returns "a".
// `firstParam.Type` returns System.Int32.

var secondParam = parameters[1].
// `secondParam.Name` returns "b".
// `secondParam.Type` returns System.Int32.

Nous voyons ici que nous pouvons tirer beaucoup d'informations d'une expression.

Mais pourquoi aurions-nous besoin de cela?

Vous avez appris qu'une arborescence d'expression est une structure de données représentant le code exécutable. Mais jusqu'à présent, nous n'avons pas répondu à la question centrale de savoir pourquoi on voudrait effectuer une telle conversion. Telle est la question que nous avons posée au début de cet article et il est maintenant temps d'y répondre.

Une requête LINQ to SQL n'est pas exécutée dans votre programme C #. Au lieu de cela, il est traduit en SQL, envoyé par un fil et exécuté sur un serveur de base de données. En d'autres termes, le code suivant n'est jamais réellement exécuté dans votre programme:
var query = from c in db.Customers where c.City == "Nantes" select new { c.City, c.CompanyName };

Il est d'abord traduit dans l'instruction SQL suivante, puis exécuté sur un serveur:
SELECT [t0].[City], [t0].[CompanyName] FROM [dbo].[Customers] AS [t0] WHERE [t0].[City] = @p0

Le code trouvé dans une expression de requête doit être traduit en une requête SQL pouvant être envoyée à un autre processus sous forme de chaîne. Dans ce cas, ce processus se trouve être une base de données SQL Server. Il sera évidemment beaucoup plus facile de traduire une structure de données telle qu'une arborescence d'expression en SQL que de traduire un code IL brut ou un code exécutable en SQL. Pour exagérer quelque peu la difficulté du problème, imaginez-vous essayer de traduire une série de zéros et de zéros en SQL!

Lorsqu'il est temps de traduire votre expression de requête en SQL, l'arborescence d'expression représentant votre requête est séparée et analysée, comme nous avons séparé notre arborescence d'expression lambda simple dans la section précédente. Certes, l’algorithme d’analyse de l’arbre d’expression LINQ vers SQL est beaucoup plus sophistiqué que celui que nous avons utilisé, mais le principe est le même. Une fois qu'il a analysé les parties de l'arborescence d'expression, LINQ les réfléchit et décide du meilleur moyen d'écrire une instruction SQL qui renverra les données demandées.

Les arbres d'expression ont été créés afin de convertir le code, tel qu'une expression de requête, en une chaîne pouvant être transmise à un autre processus et y être exécutée. C'est aussi simple que ça. Il n’ya pas de grand mystère ici, pas de baguette magique qui doit être agitée. On prend simplement du code, le convertit en données, puis analyse les données pour trouver les éléments constitutifs qui seront traduits en une chaîne pouvant être transmise à un autre processus.

Étant donné que la requête parvient au compilateur encapsulé dans une structure de données aussi abstraite, le compilateur est libre de l'interpréter de la manière qu'il souhaite. Il n'est pas obligé d'exécuter la requête dans un ordre particulier ou d'une manière particulière. Au lieu de cela, il peut analyser l'arbre d'expression, découvrir ce que vous voulez faire, puis décider comment le faire. Au moins en théorie, il est libre de prendre en compte un grand nombre de facteurs, tels que le trafic réseau actuel, la charge de la base de données, les résultats actuels disponibles, etc. En pratique, LINQ to SQL ne prend pas en compte tous ces facteurs. , mais il est en théorie libre de faire à peu près ce qu’il veut. En outre, cet arbre d’expression peut être transmis à un code personnalisé que vous écrivez à la main, qui l’analysera et le traduira en quelque chose de très différent de ce qui est produit par LINQ to SQL.

Une fois encore, nous voyons que les arbres d'expression nous permettent de représenter (exprimer?) Ce que nous voulons faire. Et nous utilisons des traducteurs qui décident comment nos expressions sont utilisées.


L’arbre d’expression auquel vous faites référence est-il un arbre d’expression évaluation?

Si oui, il s'agit d'une arborescence construite par l'analyseur. L'analyseur a utilisé Lexer / Tokenizer pour identifier les jetons du programme. L’analyseur construit l’arbre binaire à partir des jetons.

Here l'explication détaillée


Réponse courte: il est agréable de pouvoir écrire le même type de requête LINQ et de le diriger vers n'importe quelle source de données. Vous ne pourriez pas avoir une requête "Langue intégrée" sans elle.

Réponse longue: Comme vous le savez probablement, lorsque vous compilez un code source, vous le transformez d'une langue à une autre. Habituellement d’un langage de haut niveau (C #) à un levier plus bas sur (IL).

Il existe fondamentalement deux manières de procéder:

  1. Vous pouvez traduire le code en utilisant rechercher et remplacer
  2. Vous analysez le code et obtenez un arbre d'analyse.

Ce dernier est ce que tous les programmes que nous connaissons sous le nom de «compilateurs» font.

Une fois que vous avez un arbre d’analyse syntaxique, vous pouvez facilement le traduire dans n’importe quel autre langage et c’est ce que les arbres d’expression nous permettent de faire. Puisque le code est stocké sous forme de données, vous pouvez faire ce que vous voulez, mais vous voudrez probablement le traduire dans une autre langue.

À présent, dans LINQ to SQL, les arborescences d'expression sont transformées en une commande SQL, puis envoyées par le fil au serveur de base de données. Autant que je sache, ils ne font rien de vraiment chic pour traduire le code, mais ils le pourraient . Par exemple, le fournisseur de requêtes peut créer un code SQL différent en fonction des conditions du réseau.


Le DLR
Les arbres d’expression sont un ajout à C # pour prendre en charge le Dynamic Language Runtime (DLR). Le DLR est également ce qui est responsable de nous donner la méthode "var" de déclaration de variables. ( var objA = new Tree(); )

Plus sur le DLR .

Essentiellement, Microsoft souhaitait ouvrir le CLR aux langages dynamiques, tels que LISP, SmallTalk, Javascript, etc. Pour ce faire, ils devaient pouvoir analyser et évaluer les expressions à la volée. Ce n'était pas possible avant la création du DLR.

Pour revenir à ma première phrase, les arbres d’expression sont un ajout à C # qui permet d’utiliser le DLR. Auparavant, C # était un langage beaucoup plus statique: tous les types de variable devaient être déclarés en tant que type spécifique et tout le code devait être écrit au moment de la compilation.

Utilisation avec des données
Les arbres d'expression ouvrent les portes de l'inondation au code dynamique.

Disons, par exemple, que vous créez un site immobilier. Pendant la phase de conception, vous connaissez tous les filtres que vous pouvez appliquer. Pour implémenter ce code, vous avez deux choix: vous pouvez écrire une boucle qui compare chaque point de données à une série de vérifications If-Then; ou vous pouvez essayer de créer une requête dans un langage dynamique (SQL) et de la transmettre à un programme capable d'effectuer la recherche pour vous (la base de données).

Avec les arbres d'expression, vous pouvez maintenant changer le code dans votre programme - à la volée - et effectuer la recherche. Plus précisément, vous pouvez le faire via LINQ.

(Voir plus: MSDN: Comment: utiliser des arbres d'expression pour créer des requêtes dynamiques ).

Au-delà des données
Les principales utilisations des arbres d'expression sont la gestion des données. Cependant, ils peuvent également être utilisés pour du code généré dynamiquement. Donc, si vous vouliez une fonction définie dynamiquement (comme le Javascript), vous pouvez créer un arbre d’expression, le compiler et évaluer les résultats.

J'irais un peu plus en profondeur, mais ce site fait un bien meilleur travail:

Arbres d'expression en tant que compilateur

Les exemples énumérés incluent la création d'opérateurs génériques pour les types de variable, les expressions lambda à la main, le clonage superficiel de hautes performances et la copie dynamique des propriétés de lecture / écriture d'un objet à un autre.

Résumé
Les arbres d'expression sont des représentations du code compilé et évalué au moment de l'exécution. Ils permettent l'utilisation de types dynamiques, ce qui est utile pour la manipulation de données et la programmation dynamique.


IIUC, un arbre d'expression est similaire à un arbre de syntaxe abstraite, mais une expression définit généralement une valeur unique, alors qu'un AST peut représenter un programme entier (avec des classes, des packages, des fonctions, des instructions, etc.).

Quoi qu’il en soit, pour une expression (2 + 3) * 5, l’arbre est:

    *
   / \ 
  +   5
 / \
2   3

Evaluez chaque nœud de manière récursive (de bas en haut) pour obtenir la valeur du nœud racine, c'est-à-dire la valeur de l'expression.

Vous pouvez bien sûr avoir des opérateurs unaires (négation) ou trinaires (si-alors-sinon) et des fonctions (n-aire, c'est-à-dire n'importe quel nombre d'opérations) si votre langage d'expression le permet.

L'évaluation des types et le contrôle des types sont effectués sur des arbres similaires.


Les arbres d'expression sont une représentation en mémoire d'une expression, par exemple une expression arithmétique ou booléenne. Par exemple, considérons l'expression arithmétique

a + b*2

Étant donné que * a une priorité d’opérateur supérieure à +, l’arbre des expressions est construit comme suit:

    [+]
  /    \
 a     [*]
      /   \
     b     2

Ayant cet arbre, il peut être évalué pour toutes les valeurs de a et b. De plus, vous pouvez le transformer en d'autres arbres d'expression, par exemple pour dériver l'expression.

Lorsque vous implémentez un arbre d’expression, il est conseillé de créer une expression de classe. Dérivée de cela, la classe BinaryExpression serait utilisée pour toutes les expressions binaires, telles que + et *. Ensuite, vous pouvez introduire une VariableReferenceExpression pour faire référence à des variables (telles que a et b) et une autre classe ConstantExpression (pour les 2 de l’exemple).

Dans de nombreux cas, l’arbre des expressions est construit à la suite de l’analyse d’une entrée (de l’utilisateur directement ou d’un fichier). Pour évaluer l'arbre d'expression, je suggérerais d'utiliser le modèle de visiteur .






c# .net linq expression-trees