Risposta breve:
L'operatore di quotazione è un operatore che induce la semantica di chiusura sul proprio operando . Le costanti sono solo valori.
Virgolette e costanti hanno significati diversi e quindi hanno rappresentazioni diverse in un albero delle espressioni . Avere la stessa rappresentazione per due cose molto diverse è estremamente confuso e soggetto a bug.
Risposta lunga:
Considera quanto segue:
(int s)=>(int t)=>s+t
Il lambda esterno è una factory per i sommatori associati al parametro lambda esterno.
Supponiamo ora di voler rappresentare questo come un albero delle espressioni che verrà successivamente compilato ed eseguito. Quale dovrebbe essere il corpo dell'albero delle espressioni? Dipende se si desidera che lo stato compilato restituisca un delegato o un albero delle espressioni.
Cominciamo ignorando il caso poco interessante. Se desideriamo che restituisca un delegato, la questione se utilizzare Quote o Constant è un punto controverso:
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));
Il lambda ha un lambda annidato; il compilatore genera il lambda interno come delegato a una funzione chiusa sullo stato della funzione generata per il lambda esterno. Non dobbiamo più considerare questo caso.
Supponiamo di desiderare che lo stato compilato restituisca un albero delle espressioni dell'interno. Ci sono due modi per farlo: il modo facile e il modo difficile.
Il modo più difficile è dirlo invece di
(int s)=>(int t)=>s+t
quello che intendiamo veramente è
(int s)=>Expression.Lambda(Expression.Add(...
E quindi genera l'albero delle espressioni per quello , producendo questo pasticcio :
Expression.Lambda(
Expression.Call(typeof(Expression).GetMethod("Lambda", ...
blah blah blah, dozzine di righe di codice di riflessione per rendere lambda. Lo scopo dell'operatore quote è di dire al compilatore dell'albero delle espressioni che vogliamo che il dato lambda sia trattato come un albero delle espressioni, non come una funzione, senza dover generare esplicitamente il codice di generazione dell'albero delle espressioni .
Il modo più semplice è:
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 infatti, se compili ed esegui questo codice ottieni la risposta giusta.
Si noti che l'operatore di citazione è l'operatore che induce la semantica di chiusura sul lambda interno che utilizza una variabile esterna, un parametro formale del lambda esterno.
La domanda è: perché non eliminare Quote e fare in modo che faccia la stessa cosa?
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 costante non induce la semantica di chiusura. Perché dovrebbe? Hai detto che questa era una costante . È solo un valore. Dovrebbe essere perfetto come consegnato al compilatore; il compilatore dovrebbe essere in grado di generare solo un dump di quel valore nello stack dove è necessario.
Dato che non viene indotta alcuna chiusura, se si esegue questa operazione si otterrà un'eccezione "variabile 's' di tipo 'System.Int32' non definita" sull'invocazione.
(A parte: ho appena esaminato il generatore di codice per la creazione di delegati da alberi di espressioni citati, e sfortunatamente un commento che ho inserito nel codice nel 2006 è ancora lì. Cordiali saluti, il parametro esterno sollevato viene snapshot in una costante quando viene citato l'albero delle espressioni viene reificato come delegato dal compilatore runtime. C'era una buona ragione per cui ho scritto il codice in quel modo che non ricordo in questo preciso momento, ma ha il brutto effetto collaterale di introdurre la chiusura sui valori dei parametri esterni piuttosto che chiusura sulle variabili. Apparentemente il team che ha ereditato quel codice ha deciso di non correggere quel difetto, quindi se ti affidi alla mutazione di un parametro esterno chiuso osservato in un lambda interno citato compilato, rimarrai deluso. Tuttavia, poiché è una pratica di programmazione piuttosto pessima sia (1) mutare un parametro formale e (2) fare affidamento sulla mutazione di una variabile esterna, ti consiglio di cambiare il tuo programma per non usare queste due cattive pratiche di programmazione, piuttosto che in attesa di una correzione che non sembra essere imminente. Mi scuso per l'errore.)
Quindi, per ripetere la domanda:
Il compilatore C # potrebbe essere stato creato per compilare espressioni lambda annidate in un albero delle espressioni che coinvolge Expression.Constant () invece di Expression.Quote () e qualsiasi provider di query LINQ che desidera elaborare alberi delle espressioni in un altro linguaggio di query (come SQL ) potrebbe cercare una ConstantExpression con il tipo Expression invece di un UnaryExpression con lo speciale tipo di nodo Quote, e tutto il resto sarebbe lo stesso.
Hai ragione. Abbiamo potuto codificare informazione semantica che significa "inducono semantica chiusura di questo valore" dal usando il tipo dell'espressione costante come bandiera .
"Costante" avrebbe quindi il significato "usa questo valore costante, a meno che il tipo non sia un tipo di albero delle espressioni e il valore non sia un albero delle espressioni valido, nel qual caso, utilizza invece il valore che è l'albero delle espressioni risultante dalla riscrittura del all'interno dell'albero delle espressioni dato per indurre la semantica di chiusura nel contesto di qualsiasi lambda esterno in cui potremmo trovarci in questo momento.
Ma perché avremmo facciamo che cosa folle? L'operatore di citazione è un operatore follemente complicato e dovrebbe essere usato esplicitamente se lo userai. Stai suggerendo che per essere parsimoniosi nel non aggiungere un metodo factory e un tipo di nodo in più tra le diverse dozzine già presenti, aggiungiamo un bizzarro caso d'angolo alle costanti, in modo che le costanti siano a volte logicamente costanti, e talvolta vengono riscritte lambda con semantica di chiusura.
Avrebbe anche l'effetto un po 'strano che costante non significhi "usa questo valore". Supponi per qualche bizzarro motivo di volere che il terzo caso sopra compili un albero delle espressioni in un delegato che distribuisce un albero delle espressioni che ha un riferimento non riscritto a una variabile esterna? Perché? Forse perché stai testando il tuo compilatore e vuoi semplicemente passare la costante in modo da poter eseguire qualche altra analisi su di essa in seguito. La tua proposta lo renderebbe impossibile; qualsiasi costante che sia del tipo albero delle espressioni verrebbe riscritta a prescindere. Si ha una ragionevole aspettativa che "costante" significhi "usa questo valore". "Constant" è un nodo "fai quello che dico". Il processore costante '
E nota, naturalmente, che ora stai imponendo il peso della comprensione (cioè, capire che la costante ha una semantica complicata che significa "costante" in un caso e "induce una semantica di chiusura" basata su un flag che è nel sistema dei tipi ) su ogni provider che esegue l'analisi semantica di un albero delle espressioni, non solo sui provider Microsoft. Quanti di questi fornitori di terze parti avrebbero sbagliato?
"Quote" sta sventolando una grande bandiera rossa che dice "hey amico, guarda qui, sono un'espressione lambda annidata e ho una semantica stravagante se sono chiuso su una variabile esterna!" mentre "costante" sta dicendo "non sono altro che un valore; usami come meglio credi". Quando qualcosa è complicato e pericoloso, vogliamo fargli sventolare bandiere rosse, non nascondere questo fatto facendo scavare all'utente il sistema dei tipi per scoprire se questo valore è speciale o meno.
Inoltre, l'idea che evitare la ridondanza sia persino un obiettivo non è corretta. Certo, evitare ridondanze inutili e confuse è un obiettivo, ma la maggior parte delle ridondanze è una buona cosa; la ridondanza crea chiarezza. I nuovi metodi di fabbrica e i tipi di nodi sono economici . Possiamo farne quante ne abbiamo bisogno in modo che ognuna rappresenti un'operazione in modo pulito. Non abbiamo bisogno di ricorrere a brutti trucchi come "questo significa una cosa a meno che questo campo non sia impostato su questa cosa, nel qual caso significa qualcos'altro".