Réponse courte:
L'opérateur quote est un opérateur qui induit une sémantique de fermeture sur son opérande . Les constantes ne sont que des valeurs.
Les guillemets et les constantes ont des significations différentes et ont donc des représentations différentes 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.
Maintenant, supposons que nous souhaitons le représenter comme 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 souhaitez que l'état compilé renvoie un délégué ou une arborescence d'expression.
Commençons par rejeter l'affaire sans intérêt. Si nous souhaitons qu'il renvoie un délégué, la question de savoir s'il faut utiliser Quote ou Constant est un point discutable:
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 en tant que délégué à une fonction fermée sur l'état de la fonction générée pour le lambda externe. Nous n'avons plus besoin de considérer ce cas.
Supposons que nous souhaitons que l'état compilé renvoie un arbre d'expression de l'intérieur. Il y a deux façons de faire cela: la méthode facile et la méthode 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)=>Expression.Lambda(Expression.Add(...
Et puis générez l'arbre d'expression pour cela , 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 du 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, pas 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.
Notez 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 externe, un paramètre formel du lambda externe.
La question est: 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 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 être capable de simplement générer un vidage de cette valeur dans la pile où il est nécessaire.
Puisqu'il n'y a pas de fermeture induite, si vous faites cela, vous obtiendrez une exception "variable 's' de type 'System.Int32' is not defined" lors de l'invocation.
(A part: je viens de passer en revue le générateur de code pour la création de délégués à partir d'arbres d'expressions cités, et malheureusement, un commentaire que j'ai mis dans le code en 2006 est toujours là. Pour info, le paramètre externe hissé est instantané dans une constante lorsque le L'arbre d'expression est réifié en tant que délégué par le compilateur d'exécution. Il y avait 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 cela a le désagréable effet secondaire d'introduire une fermeture sur les valeurs des paramètres externes plutôt que de fermer sur des variables. Apparemment, l'équipe qui a hérité de ce code a décidé de ne pas corriger cette faille, donc si vous vous fiez à la mutation d'un paramètre externe fermé observé dans un lambda intérieur cité compilé, vous allez être déçu. Cependant, comme c'est une très mauvaise pratique de programmation à la fois (1) muter un paramètre formel et (2) 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 en attente d'un correctif qui ne semble pas être à venir. Toutes mes excuses pour l'erreur.)
Donc, pour répéter la question:
Le compilateur C # aurait pu être conçu pour compiler des expressions lambda imbriquées dans une arborescence d'expressions impliquant Expression.Constant () au lieu d'Expression.Quote (), et tout fournisseur de requêtes LINQ souhaitant traiter les arborescences d'expression dans un autre langage de requête (tel que SQL ) pourrait rechercher une ConstantExpression avec le type Expression au lieu d'une UnaryExpression avec le type de nœud Quote spécial, et tout le reste serait le même.
Vous avez raison. Nous pourrions encoder des informations sémantiques qui signifient "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 se trouve être 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 lambdas externe dans lequel nous pourrions être en ce moment.
Mais pourquoi ferions- nous ce truc fou? L'opérateur de devis est un opérateur incroyablement compliqué , et il devrait être utilisé explicitement si vous allez l'utiliser. Vous suggérez qu'afin d'être parcimonieux sur le fait de ne pas ajouter une méthode d'usine et un type de nœud supplémentaires parmi les plusieurs dizaines déjà présents, nous ajoutions un cas d'angle bizarre aux constantes, de sorte que les constantes soient parfois logiquement des constantes, et parfois elles sont réécrites lambdas avec sémantique de fermeture.
Cela aurait également l'effet quelque peu étrange que constante ne signifie pas "utiliser cette valeur". Supposons que, pour une raison étrange, vous vouliez que le troisième cas ci-dessus compile un arbre d'expression en 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 vous souhaitez simplement transmettre la constante afin de pouvoir effectuer une autre analyse plus tard. Votre proposition rendrait cela impossible; toute constante qui se trouve être de type arbre d'expression serait réécrite malgré tout. On peut raisonnablement s'attendre à ce que «constant» signifie «utiliser cette valeur». «Constant» est un nœud «fais ce que je dis». Le processeur constant ' à dire en fonction du type.
Et notez bien sûr que vous mettez maintenant le fardeau de la compréhension (c'est-à-dire comprendre que la constante a une sémantique compliquée qui signifie «constante» dans un cas et «induit une sémantique de fermeture» basée sur un drapeau qui est dans le système de types ) sur chaque fournisseur qui effectue une analyse sémantique d'un arbre d'expression, pas seulement sur les fournisseurs Microsoft. Combien de ces fournisseurs tiers se trompent?
"Quote" agite un gros drapeau rouge qui dit "Hey mon pote, regarde par ici, je suis une expression lambda imbriquée et j'ai une sémantique farfelue 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." Quand quelque chose est compliqué et dangereux, nous voulons le faire agiter des drapeaux rouges, ne pas cacher ce fait en obligeant l'utilisateur à fouiller dans le système de types afin de savoir si cette valeur est spéciale ou non.
De plus, l'idée selon laquelle éviter la redondance est même un objectif est incorrecte. Bien sûr, éviter les redondances inutiles et déroutantes 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 types de nœuds sont bon marché . Nous pouvons en fabriquer autant que nécessaire pour que chacun représente une opération proprement. Nous n'avons pas besoin de recourir à de vilaines astuces comme "cela signifie une chose à moins que ce champ ne soit défini sur cette chose, auquel cas cela signifie autre chose".