Réponse courte :
L'opérateur de citation est un opérateur dont induit une sémantique de fermeture sur son opérande . Les constantes ne sont que des valeurs.
Les citations et les constantes ont des Signification 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 confuse et sujette aux bogues.
Longue réponse :
Considérez ce qui suit :
(int s)=>(int t)=>s+t
Le lambda extérieur est une usine pour les additionneurs qui sont liés au paramètre du lambda extérieur.
Supposons maintenant que nous souhaitions représenter cela sous la forme d'un arbre d'expression qui sera ensuite compilé et exécuté. Quel 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 un arbre d'expression.
Commençons par écarter le cas inintéressant. Si nous souhaitons qu'il renvoie un délégué, la question de savoir s'il faut utiliser Quote ou Constant est sans objet :
var ps = Expression.Parameter(typeof(int), "s");
var pt = Expression.Parameter(typeof(int), "t");
var ex1 = Expression.Lambda(
Expression.Lambda(
Expression.Add(ps, pt),
pt),
ps);
var f1a = (Func<int, Func<int, int>>) ex1.Compile();
var f1b = f1a(100);
Console.WriteLine(f1b(123));
Le lambda a un lambda imbriqué ; le compilateur génère le lambda intérieur comme un délégué à une fonction fermée sur l'état de la fonction générée pour le lambda extérieur. Nous n'avons plus besoin de considérer ce cas.
Supposons que nous souhaitions que l'état compilé renvoie un fichier arbre d'expression de l'intérieur. Il y a deux façons de le faire : la méthode facile et la méthode difficile.
La manière forte est de dire qu'au lieu de
(int s)=>(int t)=>s+t
ce que nous voulons vraiment dire est
(int s)=>Expression.Lambda(Expression.Add(...
Et ensuite, générer l'arbre d'expression pour que , produisant ce désordre :
Expression.Lambda(
Expression.Call(typeof(Expression).GetMethod("Lambda", ...
bla bla bla, des dizaines de lignes de code de réflexion pour faire le lambda. Le but de l'opérateur quote est d'indiquer au compilateur d'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 d'arbre d'expression .
Le moyen le plus simple est :
var ex2 = Expression.Lambda(
Expression.Quote(
Expression.Lambda(
Expression.Add(ps, pt),
pt)),
ps);
var f2a = (Func<int, Expression<Func<int, int>>>)ex2.Compile();
var f2b = f2a(200).Compile();
Console.WriteLine(f2b(123));
Et en effet, si vous compilez et exécutez ce code, vous obtenez la bonne réponse.
Remarquez que l'opérateur quote est l'opérateur qui induit une sémantique de fermeture sur le lambda intérieur qui utilise une variable extérieure, un paramètre formel du lambda extérieur.
La question est la suivante : pourquoi ne pas éliminer Quote et faire en sorte que cela fasse la même chose ?
var ex3 = Expression.Lambda(
Expression.Constant(
Expression.Lambda(
Expression.Add(ps, pt),
pt)),
ps);
var f3a = (Func<int, Expression<Func<int, int>>>)ex3.Compile();
var f3b = f3a(300).Compile();
Console.WriteLine(f3b(123));
La constante n'induit pas de sémantique de fermeture. Pourquoi le ferait-elle ? Vous avez dit que c'était un constant . C'est juste une valeur. Elle devrait être parfaite telle qu'elle est transmise au compilateur ; ce dernier devrait être capable de générer un dump de cette valeur sur la pile où elle est nécessaire.
Comme il n'y a pas de fermeture induite, si vous faites cela, vous obtiendrez une exception "variable 's' de type 'System.Int32' non définie" lors de l'invocation.
(A propos : je viens de revoir le générateur de code pour la création de délégués à partir d'arbres d'expression cités, et malheureusement un commentaire que j'ai mis dans le code en 2006 est toujours là. Pour info, le paramètre extérieur soulevé est snapshoté en une constante lorsque l'arbre d'expression cité est réifié comme un délégué par le compilateur d'exécution. Il y avait une bonne raison pour laquelle j'ai écrit le code de cette manière, dont je ne me souviens pas à ce moment précis, mais cela a le désagréable effet secondaire d'introduire la fermeture sur valeurs des paramètres extérieurs plutôt que la fermeture sur variables . Apparemment, l'équipe qui a hérité de ce code a décidé de ne pas corriger cette faille, donc si vous comptez sur la mutation d'un paramètre extérieur fermé observé dans un lambda intérieur cité compilé, vous allez être déçu. Cependant, étant donné que c'est une assez mauvaise pratique de programmation que de (1) muter un paramètre formel et (2) de compter sur la mutation d'une variable externe, je vous recommanderais de modifier votre programme pour ne pas utiliser ces deux mauvaises pratiques de programmation, plutôt que d'attendre un correctif qui ne semble pas venir. Toutes mes excuses pour cette erreur).
Donc, pour répéter la question :
On aurait pu faire en sorte que le compilateur C# compile les expressions lambda imbriquées dans un arbre d'expression impliquant Expression.Constant() au lieu de Expression.Quote(), et tout fournisseur de requêtes LINQ qui souhaite traiter les arbres d'expression dans un autre langage de requête (tel que SQL) pourrait rechercher une ConstantExpression de type Expression au lieu d'une UnaryExpression avec le type de nœud spécial Quote, et tout le reste serait identique.
Vous avez raison. Nous pourrait encoder des informations sémantiques qui signifient "induire une sémantique de fermeture sur cette valeur" en en utilisant le type de l'expression constante comme un drapeau .
"Constante" aurait alors le sens de "utiliser cette valeur constante", sauf si le type se trouve être un type d'arbre d'expression y la valeur est un arbre d'expression valide, dans ce cas, utilisez plutôt la valeur qui est l'arbre d'expression résultant de la réécriture de l'intérieur de l'arbre d'expression donné pour induire la sémantique de fermeture dans le contexte de toutes les lambdas externes dans lesquelles nous pourrions être en ce moment.
Mais pourquoi serait on fait cette chose folle ? L'opérateur de citation est un opérateur incroyablement compliqué. et il devrait être utilisé explicitement si vous comptez l'utiliser. Vous suggérez qu'afin d'être parcimonieux et de ne pas ajouter une méthode d'usine et un type de nœud supplémentaires parmi les dizaines qui existent déjà, nous ajoutons un cas de figure bizarre aux constantes, de sorte que les constantes soient parfois logiquement des constantes, et parfois des lambdas réécrits avec une sémantique de fermeture.
Cela aurait également l'effet un peu étrange que constant ne signifie pas "utilisez cette valeur". Supposons que, pour une raison bizarre, vous recherché le troisième cas ci-dessus pour compiler un arbre d'expression dans un délégué qui distribue un arbre d'expression qui a une référence non réécrite à une variable externe ? Pourquoi ? Peut-être parce que vous testez votre compilateur et que tu veux juste faire passer la constante pour que tu puisses l'analyser plus tard. Votre proposition rendrait cela impossible ; toute constante qui se trouve être de type expression tree serait réécrite de toute façon. On peut raisonnablement s'attendre à ce que "constant" signifie "utiliser cette valeur". "Constant" est un nœud "faites ce que je dis". Le travail du processeur de constantes n'est pas de deviner ce que vous voulez faire. signifiait à dire en fonction du type.
Et notez bien sûr que vous mettez maintenant la charge de la compréhension (c'est-à-dire comprendre que constant a une sémantique compliquée qui signifie "constant" dans un cas et "induire une sémantique de fermeture" basée sur un drapeau qui est dans le système de type ) sur chaque qui effectue une analyse sémantique d'un arbre d'expression, et pas seulement sur les fournisseurs de Microsoft. Combien de ces fournisseurs tiers se tromperaient ?
"Citer", c'est agiter un grand drapeau rouge qui dit "hé mon pote, regarde par ici, je suis une expression lambda imbriquée et j'ai une sémantique bizarre si je suis fermé sur une variable externe !" alors que "Constant" dit "je ne suis rien de plus qu'une valeur ; utilise-moi comme bon te semble". Lorsqu'une chose est compliquée et dangereuse, nous voulons qu'elle émette des signaux d'alarme, et non pas qu'elle cache ce fait en obligeant l'utilisateur à fouiller dans la base de données de l système de type afin de savoir si cette valeur est spéciale ou non.
En outre, l'idée qu'éviter la redondance soit même un objectif est incorrecte. Bien sûr, éviter la redondance inutile et déroutante est un objectif, mais la plupart des redondances sont une bonne chose ; la redondance crée de la clarté. Les nouvelles méthodes d'usine et les nouveaux types de nœuds sont bon marché . Nous pouvons en faire autant que nécessaire pour que chacun représente proprement une opération. Nous n'avons pas besoin d'avoir recours à des trucs méchants comme "ceci signifie une chose à moins que ce champ ne prenne cette valeur, auquel cas cela signifie autre chose".