Resposta curta:
O operador quote é um operador que induz a semântica de fechamento em seu operando . Constantes são apenas valores.
Aspas e constantes têm significados diferentes e, portanto, têm representações diferentes em uma árvore de expressão . Ter a mesma representação para duas coisas muito diferentes é extremamente confuso e sujeito a erros.
Resposta longa:
Considere o seguinte:
(int s)=>(int t)=>s+t
O lambda externo é uma fábrica de somadores vinculados ao parâmetro do lambda externo.
Agora, suponha que desejamos representar isso como uma árvore de expressão que será posteriormente compilada e executada. Qual deve ser o corpo da árvore de expressão? Depende se você deseja que o estado compilado retorne um delegado ou uma árvore de expressão.
Vamos começar descartando o caso desinteressante. Se desejarmos que ele retorne um delegado, a questão de usar Quote ou Constant é um ponto discutível:
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));
O lambda tem um lambda aninhado; o compilador gera o lambda interno como um delegado para uma função fechada sobre o estado da função gerada para o lambda externo. Não precisamos mais considerar este caso.
Suponha que desejamos que o estado compilado retorne uma árvore de expressão do interior. Existem duas maneiras de fazer isso: a maneira mais fácil e a mais difícil.
A maneira mais difícil é dizer isso em vez de
(int s)=>(int t)=>s+t
o que realmente queremos dizer é
(int s)=>Expression.Lambda(Expression.Add(...
E então gere a árvore de expressão para isso , produzindo esta bagunça :
Expression.Lambda(
Expression.Call(typeof(Expression).GetMethod("Lambda", ...
blá blá blá, dezenas de linhas de código de reflexão para fazer o lambda. O objetivo do operador quote é dizer ao compilador da árvore de expressão que queremos que o lambda fornecido seja tratado como uma árvore de expressão, não como uma função, sem ter que gerar explicitamente o código de geração da árvore de expressão .
A maneira mais fácil é:
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));
E, de fato, se você compilar e executar esse código, obterá a resposta certa.
Observe que o operador de aspas é o operador que induz a semântica de fechamento no lambda interno que usa uma variável externa, um parâmetro formal do lambda externo.
A questão é: por que não eliminar o Quote e fazer com que isso faça a mesma coisa?
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));
A constante não induz a semântica de fechamento. Por que deveria? Você disse que isso era uma constante . É apenas um valor. Deve ser perfeito conforme entregue ao compilador; o compilador deve ser capaz de gerar apenas um dump desse valor para a pilha onde ele é necessário.
Visto que não há fechamento induzido, se você fizer isso, obterá uma exceção "variável 's' do tipo 'System.Int32' não está definido" na chamada.
(À parte: acabei de revisar o gerador de código para a criação de delegado de árvores de expressão entre aspas e, infelizmente, um comentário que coloquei no código em 2006 ainda está lá. FYI, o parâmetro externo içado é instantâneo em uma constante quando o a árvore de expressão é reificada como um delegado pelo compilador de tempo de execução. Há uma boa razão para eu ter escrito o código dessa maneira, que não me lembro neste exato momento, mas tem o efeito colateral desagradável de introduzir o fechamento sobre os valores dos parâmetros externos ao invés de fechamento sobre variáveis. Aparentemente, a equipe que herdou esse código decidiu não corrigir essa falha, então se você está contando com a mutação de um parâmetro externo fechado sendo observado em um lambda interno compilado entre aspas, você ficará desapontado. No entanto, uma vez que é uma prática de programação muito ruim (1) alterar um parâmetro formal e (2) confiar na mutação de uma variável externa, eu recomendaria que você altere seu programa para não usar essas duas práticas de programação ruins, em vez de esperando por uma correção que não parece estar próxima. Desculpas pelo erro.)
Então, para repetir a pergunta:
O compilador C # poderia ter sido feito para compilar expressões lambda aninhadas em uma árvore de expressão envolvendo Expression.Constant () em vez de Expression.Quote (), e qualquer provedor de consulta LINQ que deseja processar árvores de expressão em alguma outra linguagem de consulta (como SQL ) poderia procurar uma ExpressãoConstante com o tipo Expressão em vez de uma ExpressãoUnária com o tipo de nó Citação especial e todo o resto seria o mesmo.
Você está certo. Nós poderia codificar a informação semântica que significa "induzir a semântica de fechamento sobre este valor" por usando o tipo da expressão constante como uma bandeira .
"Constante" teria então o significado "use este valor constante, a menos que o tipo seja um tipo de árvore de expressão e o valor seja uma árvore de expressão válida, nesse caso, use o valor que é a árvore de expressão resultante da reescrita de interior da árvore de expressão fornecida para induzir a semântica de fechamento no contexto de quaisquer lambdas externos em que possamos estar agora.
Mas por que teria que fazer essa coisa louca? O operador quote é um operador extremamente complicado e deve ser usado explicitamente se você for usá-lo. Você está sugerindo que, para ser parcimonioso sobre não adicionar um método de fábrica extra e tipo de nó entre as várias dezenas já existentes, que adicionemos um caso estranho bizarro às constantes, de modo que as constantes às vezes sejam logicamente constantes e às vezes sejam reescritas lambdas com semântica de fechamento.
Também teria o efeito um tanto estranho de constante não significar "usar este valor". Suponha que por alguma razão bizarra você queira que o terceiro caso acima compile uma árvore de expressão em um delegado que distribui uma árvore de expressão que tem uma referência não reescrita a uma variável externa. Por quê? Talvez porque você está testando seu compilador e deseja apenas passar a constante para que possa realizar alguma outra análise depois. Sua proposta tornaria isso impossível; qualquer constante que seja do tipo de árvore de expressão seria reescrita independentemente. Tem-se uma expectativa razoável de que "constante" significa "use este valor". "Constante" é um nó "faça o que eu digo". O processador constante ' dizer com base no tipo.
E observe, é claro, que agora você está colocando o fardo do entendimento (isto é, entender que constante tem semântica complicada que significa "constante" em um caso e "induzir semântica de fechamento" com base em um sinalizador que está no sistema de tipos ) em cada provedor que faz análise semântica de uma árvore de expressão, não apenas em provedores Microsoft. Quantos desses fornecedores terceirizados errariam?
"Quote" está acenando uma grande bandeira vermelha que diz "ei, amigo, olhe aqui, sou uma expressão lambda aninhada e tenho uma semântica maluca se estiver fechado em uma variável externa!" enquanto "Constante" é dizer "Não sou nada mais do que um valor; use-me como achar melhor." Quando algo é complicado e perigoso, queremos fazê-lo acenar com bandeiras vermelhas, não escondendo esse fato fazendo o usuário vasculhar o sistema de tipos para descobrir se esse valor é especial ou não.
Além disso, a ideia de que evitar a redundância é até mesmo uma meta é incorreta. Claro, evitar redundâncias desnecessárias e confusas é uma meta, mas a maioria das redundâncias é uma coisa boa; redundância cria clareza. Novos métodos de fábrica e tipos de nós são baratos . Podemos fazer quantos forem necessários para que cada um represente uma operação de forma clara. Não precisamos recorrer a truques desagradáveis como "isso significa uma coisa, a menos que este campo seja definido para essa coisa, caso em que significa outra coisa."