Qu'est-ce que Expression.Quote () fait que Expression.Constant () ne peut pas déjà faire?

c# expression-trees

Question

Remarque: je suis conscient de la question précédente « Quel est le but de la méthode Expression.Quote de LINQ? « mais si vous lisez, vous verrez que cela ne répond pas à ma question.

Je comprends quel est l'objectif déclaré de Expression.Quote() . Cependant, Expression.Constant() peut être utilisé dans le même but (en plus de tous les objectifs pour lesquels Expression.Constant() est déjà utilisé). Par conséquent, je ne comprends pas pourquoi Expression.Quote() est nécessaire.

Pour illustrer cela, j’ai écrit un exemple rapide où l’on utilisait habituellement Quote (voir la ligne marquée d’un point d’exclamation), mais j’ai plutôt utilisé Constant et cela a fonctionné aussi bien:

string[] array = { "one", "two", "three" };

// This example constructs an expression tree equivalent to the lambda:
// str => str.AsQueryable().Any(ch => ch == 'e')

Expression<Func<char, bool>> innerLambda = ch => ch == 'e';

var str = Expression.Parameter(typeof(string), "str");
var expr =
    Expression.Lambda<Func<string, bool>>(
        Expression.Call(typeof(Queryable), "Any", new Type[] { typeof(char) },
            Expression.Call(typeof(Queryable), "AsQueryable",
                            new Type[] { typeof(char) }, str),
            // !!!
            Expression.Constant(innerLambda)    // <--- !!!
        ),
        str
    );

// Works like a charm (prints one and three)
foreach (var str in array.AsQueryable().Where(expr))
    Console.WriteLine(str);

La sortie de expr.ToString() est la même pour les deux (que j'utilise Constant ou Quote ).

Compte tenu des observations ci-dessus, il apparaît que Expression.Quote() est redondant. Le compilateur C # aurait pu compiler des expressions lambda imbriquées dans un arbre d’ Expression.Constant() impliquant Expression.Constant() au lieu d’ Expression.Quote() , ainsi que tout fournisseur de requêtes LINQ souhaitant traiter les arbres d’expression dans un autre langage de requête (tel que SQL). ) pourrait rechercher une ConstantExpression de type Expression<TDelegate> au lieu d’une UnaryExpression avec le type de noeud Quote spécial, et tout le reste serait identique.

Qu'est-ce que je rate? Pourquoi Expression.Quote() et le type de nœud Quote spécial pour UnaryExpression inventés?

Réponse acceptée

Réponse courte:

L'opérateur de devis est un opérateur qui induit une sémantique de fermeture sur son opérande . Les constantes ne sont que des valeurs.

Les citations et les constantes ont des significations différentes et ont donc différentes représentations dans un arbre d'expression . Avoir la même représentation pour deux choses très différentes est extrêmement déroutant et sujet aux bogues.

Longue réponse:

Considérer ce qui suit:

(int s)=>(int t)=>s+t

Le lambda externe est une fabrique pour les additionneurs liés au paramètre du lambda externe.

Supposons maintenant que nous souhaitons représenter cela sous la forme d'un arbre d'expression qui sera ultérieurement compilé et exécuté. Que devrait être le corps de l'arbre d'expression? Cela dépend si vous voulez que l'état compilé renvoie un délégué ou une arborescence d'expression.

Commençons par rejeter le cas sans intérêt. Si nous souhaitons qu’il retourne un délégué, la question de savoir si Quote ou Constant doit être utilisé est sans objet:

(int s)=>(int t)=>s+t

Le lambda a un lambda imbriqué; le compilateur génère le lambda intérieur en tant que délégué d'une fonction fermée sur l'état de la fonction générée pour le lambda extérieur. Nous n'avons plus besoin d'examiner ce cas.

Supposons que nous souhaitions que l'état compilé retourne un arbre d'expression de l'intérieur. Il y a deux façons de le faire: le moyen facile et le moyen difficile.

Le plus dur est de dire qu'au lieu de

(int s)=>(int t)=>s+t

ce que nous voulons vraiment dire, c'est

(int s)=>(int t)=>s+t

Et puis générer l'arbre d'expression pour cela, la production de ce gâchis:

(int s)=>(int t)=>s+t

bla bla bla, des dizaines de lignes de code de réflexion pour faire le lambda. Le rôle de l'opérateur quote est d'indiquer au compilateur de l'arbre d'expression que nous voulons que le lambda donné soit traité comme un arbre d'expression, et non comme une fonction, sans avoir à générer explicitement le code de génération de l'arbre d'expression .

Le moyen le plus simple est:

(int s)=>(int t)=>s+t

Et en effet, si vous compilez et exécutez ce code, vous obtenez la bonne réponse.

Notez que l'opérateur de guillemet est l'opérateur qui induit la sémantique de fermeture sur le lambda intérieur, qui utilise une variable externe, un paramètre formel du lambda externe.

La question est: pourquoi ne pas éliminer Quote et faire en sorte que ce soit la même chose?

(int s)=>(int t)=>s+t

La constante n'induit pas de sémantique de fermeture. Pourquoi devrait-il? Vous avez dit que c'était une constante . C'est juste une valeur. Il devrait être parfait tel que remis au compilateur; le compilateur devrait pouvoir simplement générer un dump de cette valeur dans la pile où cela est nécessaire.

Comme il n'y a pas de fermeture induite, si vous faites cela, vous obtiendrez une "variable" de type "System.Int32" n'est pas définie "exception à l'invocation.

(A part: je viens de passer en revue le générateur de code pour la création de délégué à partir d'arbres d'expression cités, et malheureusement, un commentaire que j'ai mis dans le code en 2006 est toujours là. FYI, le paramètre externe hissé est capturé dans une constante lorsque le Le compilateur d’exécution réifie l’arbre des expressions en tant que délégué. C’est une bonne raison pour laquelle j’ai écrit le code de cette façon dont je ne me souviens pas à ce moment précis, mais il a pour effet pervers d’introduire la fermeture par-dessus les valeurs des paramètres externes. plutôt que la fermeture sur les variables. Apparemment , l'équipe qui a hérité que le code a décidé de ne pas corriger ce défaut, donc si vous comptez sur la mutation d'un paramètre fermé sur externe étant observée dans un intérieur lambda cité compilé, vous allez être déçu Cependant, comme c’est une très mauvaise pratique de programmation de (1) muter un paramètre formel et (2) de s’appuyer sur la mutation d’une variable externe, je vous recommanderais de changer votre programme pour ne pas utiliser ces deux mauvais pratiques de programmation, plutôt que d’attendre une solution qui ne semble pas être à venir. Toutes mes excuses pour l'erreur.)

Donc, pour répéter la question:

Le compilateur C # aurait pu compiler des expressions lambda imbriquées dans un arbre d’expression impliquant Expression.Constant () au lieu d’Expression.Quote (), ainsi que tout fournisseur de requêtes LINQ souhaitant traiter les arbres d’expression dans un autre langage de requête ) pourrait rechercher une ConstantExpression avec le type Expression au lieu d’une expression UnaryExpression avec le type de noeud spécial Quote, et tout le reste serait identique.

Vous avez raison. Nous pourrions coder une information sémantique qui signifie "induire une sémantique de fermeture sur cette valeur" en utilisant le type de l'expression constante comme indicateur .

"Constante" aurait alors le sens "utiliser cette valeur constante, sauf si le type est un type d'arbre d'expression et que la valeur est un arbre d'expression valide, auquel cas utilisez plutôt la valeur qui est l'arbre d'expression résultant de la réécriture du intérieur de l’arbre d’expression donné pour induire une sémantique de fermeture dans le contexte de tout lambda externe dans lequel nous pourrions être en ce moment.

Mais pourquoi ferions- nous cette chose folle? L'opérateur de devis est un opérateur incroyablement compliqué , et il doit être utilisé explicitement si vous souhaitez l'utiliser. Vous suggérez que, pour ne pas ajouter une méthode d'usine et un type de nœud parmi les dizaines déjà existantes, nous ajoutons un casse bizarre aux constantes, de sorte que les constantes soient parfois logiquement constantes et parfois réécrites. Lambdas avec la sémantique de fermeture.

Cela aurait aussi l’effet un peu étrange que Constante ne signifie pas "utiliser cette valeur". Supposons que, pour une raison quelconque, vous souhaitiez que le troisième cas ci-dessus compile une arborescence d'expression en un délégué qui distribue une arborescence d'expression contenant une référence non réécrite à une variable externe? Pourquoi? Peut-être parce que vous testez votre compilateur et que vous voulez simplement transmettre la constante afin que vous puissiez effectuer une autre analyse plus tard. Votre proposition rendrait cela impossible. toute constante qui se trouve être du type arbre d'expression serait réécrite indépendamment. On peut raisonnablement s'attendre à ce que "constant" signifie "utilise cette valeur". "Constant" est un noeud "fais ce que je dis". Le travail du processeur constante est de ne pas deviner ce que vous vouliez dire en fonction du type.

Et notez bien sûr que vous mettez maintenant le fardeau de la compréhension (c’est-à-dire que comprendre que constante a une sémantique compliquée qui signifie "constante" dans un cas et "induire une sémantique de fermeture" basée sur un drapeau qui est dans le système de types ) à chaque fois. fournisseur qui effectue une analyse sémantique d’un arbre d’expression, et pas seulement sur les fournisseurs Microsoft. Combien de ces fournisseurs tiers se tromperaient?

"Quote" brandit un grand drapeau rouge qui dit "hé mon pote, regarde ici, je suis une expression lambda imbriquée et j'ai une sémantique délirante si je suis fermé sur une variable externe!" alors que "Constant" dit "je ne suis rien de plus qu'une valeur; utilisez-moi comme bon vous semble." Lorsque quelque chose est compliqué et dangereux, nous voulons faire en sorte que nous levions les drapeaux rouges, sans le cacher, en obligeant l'utilisateur à fouiller dans le système de typographie afin de déterminer si cette valeur est spéciale ou non.

De plus, l'idée d'éviter la redondance est même un objectif est incorrecte. Éviter les redondances inutiles et déroutantes est certes un objectif, mais la plupart des redondances sont une bonne chose. la redondance crée la clarté. Les nouvelles méthodes d'usine et les types de nœuds ne coûtent pas cher . Nous pouvons en faire autant que nécessaire pour que chacune représente une opération proprement. Nous n'avons pas besoin de recourir à des astuces désagréables du type "cela signifie une chose à moins que ce champ ne soit défini sur cette chose, auquel cas cela signifie autre chose".


Réponse populaire

Cette question a déjà reçu une excellente réponse. De plus, j'aimerais signaler une ressource qui peut s'avérer utile pour les questions sur les arbres d'expression:

Il existe un projet CodePlex de Microsoft appelé Dynamic Language Runtime . Sa documentation comprend le document intitulé "Expression Trees v2 Spec" , qui est exactement ce qui suit: Spécification des arbres d’expression LINQ dans .NET 4.

Par exemple, il est dit ce qui suit à propos de Expression.Quote :

4.4.42 Citation

Utilisez Quote dans UnaryExpressions pour représenter une expression qui a une valeur "constante" de type Expression. Contrairement à un nœud constant, le nœud Quote traite spécialement des nœuds ParameterExpression contenus. Si un nœud ParameterExpression contenu déclare un local qui serait fermé dans l'expression résultante, Quote remplace ParameterExpression dans ses emplacements de référence. Au moment de l'exécution, lorsque le noeud Quote est évalué, il remplace les références de variable de fermeture par les noeuds de référence ParameterExpression, puis renvoie l'expression citée. […] (P. 63–64)




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi