Che cosa Expression.Quote () fa che Expression.Constant () non può già fare?

c# expression-trees

Domanda

Nota: sono a conoscenza della domanda precedente. " Qual è lo scopo del metodo Expression.Quote di LINQ? â € , ma se continui a leggere vedrai che non risponde alla mia domanda.

Capisco qual è lo scopo dichiarato di Expression.Quote() . Tuttavia, Expression.Constant() può essere utilizzato per lo stesso scopo (oltre a tutti gli scopi per cui Expression.Constant() è già utilizzato). Pertanto, non capisco perché Expression.Quote() sia necessario.

Per dimostrarlo, ho scritto un rapido esempio in cui si usa abitualmente Quote (vedi la linea contrassegnata da punti esclamativi), ma ho usato Constant invece e ha funzionato egualmente bene:

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);

L'output di expr.ToString() è uguale per entrambi (anche se utilizzo Constant o Quote ).

Date le osservazioni precedenti, sembra che Expression.Quote() sia ridondante. Il compilatore C # potrebbe essere stato creato per compilare espressioni lambda annidate in una struttura di espressioni che coinvolgono Expression.Constant() anziché Expression.Quote() e qualsiasi provider di query LINQ che desidera elaborare alberi di espressioni in un altro linguaggio di query (come SQL ) potrebbe cercare un ConstantExpression con tipo Expression<TDelegate> invece di un UnaryExpression con il tipo di nodo Quote speciale e tutto il resto sarebbe lo stesso.

Cosa mi manca? Perché è stato inventato Expression.Quote() e il tipo di nodo Quote speciale per UnaryExpression ?

Risposta accettata

Risposta breve:

L'operatore di quotatura è un operatore che induce la semantica di chiusura sul suo operando . Le costanti sono solo valori.

Le virgolette e le costanti hanno significati diversi e quindi hanno rappresentazioni differenti in un albero di espressioni . Avere la stessa rappresentazione per due cose molto diverse è estremamente confuso e incline agli insetti.

Risposta lunga:

Considera quanto segue:

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

Il lambda esterno è una fabbrica per i sommatori che sono legati al parametro lambda esterno.

Ora, supponiamo di voler rappresentare questo come un albero di espressioni che verrà successivamente compilato ed eseguito. Quale dovrebbe essere il corpo dell'albero dell'espressione? Dipende se vuoi che lo stato compilato restituisca un delegato o un albero di espressioni.

Iniziamo liquidando il caso non interessante. Se desideriamo che restituisca un delegato, la questione se utilizzare Quote o Costante è 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 l'interno lambda come delegato a una funzione chiusa sullo stato della funzione generata per il lambda esterno. Dobbiamo considerare questo caso non più.

Supponiamo che desideriamo che lo stato compilato restituisca un albero di espressione dell'interno. Ci sono due modi per farlo: il modo semplice e il modo difficile.

Il modo difficile è quello di dire che invece di

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

ciò che intendiamo veramente è

(int s)=>Expression.Lambda(Expression.Add(...

E poi genera l'albero delle espressioni per quello , producendo questo casino :

        Expression.Lambda(
            Expression.Call(typeof(Expression).GetMethod("Lambda", ...

bla bla bla, dozzine di linee di codice di riflessione per fare il lambda. Lo scopo dell'operatore di quotatura è di dire al compilatore di albero di espressioni che vogliamo che il dato lambda sia trattato come un albero di espressioni, non come una funzione, senza dover generare esplicitamente il codice di generazione dell'albero dell'espressione .

Il modo 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 in effetti, se si compila e si esegue questo codice si ottiene la risposta giusta.

Si noti che l'operatore di quotatura è 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 Citazione e far sì che facciano 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 della 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 semplicemente un dump di quel valore nello stack in cui è necessario.

Poiché non vi è alcuna chiusura indotta, se si esegue questa operazione si otterrà una "variabile" di tipo "System.Int32" non definita "eccezione per l'invocazione.

(A parte: ho appena esaminato il generatore di codice per la creazione di delegati da alberi di espressione citati, e sfortunatamente un commento che ho inserito nel codice nel 2006 è ancora lì. FYI, il parametro esterno sollevato viene catturato in una costante quando il quotato L'espressione tree è stata delegata dal compilatore di runtime: c'era una buona ragione per cui ho scritto il codice in quel modo che non ricordo in questo momento esatto, ma ha il brutto effetto collaterale dell'introduzione della chiusura sui valori dei parametri esterni piuttosto che la chiusura sopra variabili. a quanto pare il team che ha ereditato il codice che ha deciso di non correggere quel difetto, quindi se si fa affidamento su di mutazione di un chiuso-over parametro esterno essere osservati in una lambda interno citato compilato, si sta andando ad essere deluso Tuttavia, poiché è una pratica di programmazione piuttosto negativa ad entrambi (1) modificare un parametro formale e (2) fare affidamento sulla mutazione di una variabile esterna, ti consiglio di cambiare il tuo programma in modo da non usare questi due cattivi pratiche di programmazione, piuttosto che aspettare una correzione che non sembra essere imminente. Ci scusiamo per l'errore.)

Quindi, per ripetere la domanda:

Il compilatore C # potrebbe essere stato creato per compilare espressioni lambda annidate in una struttura di espressioni che coinvolgono Expression.Constant () anziché Expression.Quote () e qualsiasi provider di query LINQ che desidera elaborare alberi di espressioni in un altro linguaggio di query (come SQL ) potrebbe cercare un ConstantExpression con tipo Expression invece di un UnaryExpression con il tipo di nodo Quote speciale e tutto il resto sarebbe lo stesso.

Hai ragione. Potremmo codificare informazione semantica che significa "induce semantica chiusura di questo valore" utilizzando 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 di espressione e il valore sia un albero di espressioni valido, nel qual caso, usa invece il valore che è l'albero dell'espressione risultante dalla riscrittura del l'interno dell'albero delle espressioni dato per indurre la semantica della chiusura nel contesto di qualsiasi lambda esterno in cui potremmo essere in questo momento.

Ma perché dovremmo fare quella cosa pazzesca? L'operatore di quotatura è un operatore follemente complicato e dovrebbe essere usato esplicitamente se lo si utilizzerà. Stai suggerendo che per essere parsimonioso di non aggiungere un metodo factory e un tipo di nodo in più tra le diverse dozzine già presenti, aggiungiamo un caso d'angolo bizzarro alle costanti, cosicché le costanti a volte sono logicamente costanti, e qualche volta vengono riscritte lambda con semantica di chiusura.

Avrebbe anche l'effetto un po 'strano che costante non significhi "usare questo valore". Supponiamo per qualche strana ragione che volevi il terzo caso sopra per compilare un albero di espressioni in un delegato che distribuisce un albero di 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 su così via in modo da poter eseguire qualche altra analisi su di esso in seguito. La tua proposta lo renderebbe impossibile; qualsiasi costante che capita di essere di tipo albero di espressione dovrebbe essere riscritta a prescindere. Si ha una ragionevole aspettativa che "costante" significhi "usare questo valore". "Costante" è un nodo "fai ciò che dico". Il lavoro costante del processore non è quello di indovinare cosa intendi dire in base al tipo.

E naturalmente si nota che ora si sta ponendo l'onere della comprensione (cioè, capire che la costante ha una semantica complicata che significa "costante" in un caso e "induce semantica di chiusura" basata su una bandiera che si trova nel sistema dei tipi ) su ogni provider che esegue l'analisi semantica di un albero di espressioni, non solo su provider Microsoft. Quanti di questi fornitori di terze parti potrebbero sbagliare?

"Quote" sta sventolando una grande bandiera rossa che dice "hey amico, guarda qui, io sono un'espressione lambda annidata e ho una semantica stravagante se sono chiuso su una variabile esterna!" mentre "Constant" sta dicendo "Non sono altro che un valore, usami come ritieni opportuno". Quando qualcosa è complicato e pericoloso, vogliamo farlo sventolare bandiere rosse, non nascondendo quel fatto facendo scorrere l'utente attraverso il sistema dei tipi per scoprire se questo valore è speciale o no.

Inoltre, l'idea che evitare la ridondanza sia anche un obiettivo non è corretta. Certo, evitare una ridondanza inutile e confusa è un obiettivo, ma la maggior parte della ridondanza è una buona cosa; la ridondanza crea chiarezza. Nuovi metodi di produzione e tipi di nodi sono economici . Possiamo fare tutto ciò di cui abbiamo bisogno in modo che ognuno rappresenti una operazione in modo pulito. Non abbiamo bisogno di ricorrere a brutti scherzi come "questo significa una cosa a meno che questo campo non sia impostato su questa cosa, nel qual caso significa qualcos'altro".


Risposta popolare

Questa domanda ha già ricevuto un'ottima risposta. Vorrei inoltre indicare una risorsa che può rivelarsi utile con le domande sugli alberi di espressione:

C'è un progetto CodePlex di Microsoft chiamato Dynamic Language Runtime . La sua documentazione include il documento intitolato "Expression Trees v2 Spec" , che è esattamente questo: la specifica per gli alberi di espressione LINQ in .NET 4.

Ad esempio, si dice quanto segue su Expression.Quote :

4.4.42 Citazione

Utilizzare la citazione in UnariaExpressioni per rappresentare un'espressione che ha un valore "costante" di tipo Espressione. A differenza di un nodo Costante, il nodo Quote gestisce in modo specifico i nodi ParameterExpression. Se un nodo ParameterExpression contenuto dichiara un locale che verrebbe chiuso nell'espressione risultante, allora Quote sostituisce ParameterExpression nelle sue posizioni di riferimento. In fase di esecuzione quando viene valutato il nodo Quotazione, sostituisce i riferimenti alle variabili di chiusura per i nodi di riferimento ParameterExpression e quindi restituisce l'espressione quotata. [...] (pagina 63-64)



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché