Was macht Expression.Quote () mit Expression.Constant () noch nicht?

c# expression-trees

Frage

Hinweis: Mir ist die frühere Frage bekannt: " Was ist der Zweck der LINQ-Methode Expression.Quote? â €, aber wenn Sie weiter lesen Sie werden sehen , dass es doesnâ € ™ t meine Frage beantworten.

Ich verstehe, was der angegebene Zweck von Expression.Quote() ist. Expression.Constant() kann jedoch für denselben Zweck verwendet werden (zusätzlich zu allen Zwecken, für die Expression.Constant() bereits verwendet wird). Daher verstehe ich nicht, warum Expression.Quote() überhaupt erforderlich ist.

Um dies zu demonstrieren, habe ich ein kurzes Beispiel geschrieben, wo man normalerweise Quote (siehe die mit Ausrufezeichen markierte Zeile), aber ich habe stattdessen Constant verwendet und es hat gleich gut funktioniert:

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

Die Ausgabe von expr.ToString() ist auch für beide gleich (ob ich Constant oder Quote ).

Angesichts der obigen Beobachtungen scheint Expression.Quote() überflüssig zu sein. Der C # -Compiler könnte zum Verschachteln von geschachtelten Lambda-Ausdrücken in eine Ausdrucksbaumstruktur mit Expression.Constant() anstelle von Expression.Quote() und jedem LINQ-Abfrageprovider, der Ausdrucksbäume in eine andere Abfragesprache (wie SQL) verarbeiten möchte, erstellt werden ) für einen Blick könnte ConstantExpression mit Typ Expression<TDelegate> anstelle einer UnaryExpression mit dem speziellen Quote Knotentyp, und alles anderes gleich sein würde.

Was vermisse ich? Warum wurde Expression.Quote() und die spezielle Quote Knotentyp für UnaryExpression erfunden?

Akzeptierte Antwort

Kurze Antwort:

Der Zitatoperator ist ein Operator, der eine Schließsemantik für seinen Operanden induziert . Konstanten sind nur Werte.

Zitate und Konstanten haben unterschiedliche Bedeutungen und haben daher unterschiedliche Repräsentationen in einem Ausdrucksbaum . Die gleiche Darstellung für zwei sehr unterschiedliche Dinge zu haben ist extrem verwirrend und fehleranfällig.

Lange Antwort:

Folgendes berücksichtigen:

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

Das äußere Lambda ist eine Factory für Addierer, die an den äußeren Lambda-Parameter gebunden sind.

Angenommen, wir möchten dies als einen Ausdrucksbaum darstellen, der später kompiliert und ausgeführt wird. Was sollte der Körper des Ausdrucksbaums sein? Es hängt davon ab, ob der kompilierte Zustand einen Delegaten oder eine Ausdrucksstruktur zurückgeben soll.

Beginnen wir damit, den uninteressanten Fall zu verwerfen. Wenn wir wünschen, dass ein Delegat zurückgegeben wird, ist die Frage, ob Quote oder Constant verwendet werden soll, ein strittiger Punkt:

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

Das Lambda hat ein verschachteltes Lambda; Der Compiler generiert das innere Lambda als Delegat für eine Funktion, die über den Zustand der für das äußere Lambda erzeugten Funktion geschlossen ist. Wir müssen diesen Fall nicht mehr berücksichtigen.

Angenommen, der kompilierte Zustand soll einen Ausdrucksbaum des Interieurs zurückgeben. Dafür gibt es zwei Möglichkeiten: den einfachen Weg und den harten Weg.

Der harte Weg ist, das zu sagen statt

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

Was wir wirklich meinen ist

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

Und dann erzeugen , um den Ausdrucksbaum für das, dieses Chaos produziert:

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

bla bla bla, Dutzende von Zeilen der Reflexion Code, um das Lambda zu machen. Der Zweck des Anführungszeichen-Operators besteht darin, dem Ausdrucksbaum-Compiler mitzuteilen, dass das gegebene Lambda als Ausdrucksbaum behandelt werden soll, und nicht als eine Funktion, ohne explizit den Ausdrucksbaum-Erzeugungscode erzeugen zu müssen .

Der einfache Weg ist:

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

Und in der Tat, wenn Sie diesen Code kompilieren und ausführen, erhalten Sie die richtige Antwort.

Beachten Sie, dass der Operator quote der Operator ist, der eine Schluss-Semantik für das innere Lambda induziert, der eine äußere Variable, einen formalen Parameter des äußeren Lambda, verwendet.

Die Frage ist: Warum nicht Quote eliminieren und das Gleiche machen lassen?

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

Die Konstante induziert keine Semantik zum Schließen. Warum sollte es? Du sagtest, das sei eine Konstante . Es ist nur ein Wert. Es sollte perfekt sein, wenn es dem Compiler übergeben wird; Der Compiler sollte in der Lage sein, einfach einen Dump dieses Wertes zu dem Stack zu erzeugen, wo er benötigt wird.

Da keine Schließung induziert wird, erhalten Sie eine "Variable" vom Typ "System.Int32" ist nicht definiert "Ausnahme beim Aufruf.

(Nebenbei: Ich habe gerade den Code - Generator für die Delegierten Schaffung von zitierte Ausdruck Bäume überprüft und leider ein Kommentar, den ich in den Code setzen im Jahr 2006 ist immer noch da FYI, die gehisst äußere Parameter wird in eine konstante Snapshot von ihr erstellt , wenn die zitierte. Der Ausdrucksbaum wird vom Laufzeitcompiler als Delegat verdinglicht Es gab einen guten Grund, warum ich den Code auf diese Weise geschrieben habe, an den ich mich in diesem Moment nicht erinnere, aber er hat den unangenehmen Nebeneffekt, dass die Werte der äußeren Parameter geschlossen werden Anstatt die Variable zu schließen, hat das Team, das diesen Code geerbt hat, beschlossen, diesen Fehler nicht zu beheben. Wenn Sie also auf die Mutation eines geschlossenen äußeren Parameters in einem kompilierten internen Lambda angewiesen sind, werden Sie enttäuscht sein Da es jedoch eine ziemlich schlechte Programmierpraxis ist, sowohl einen formalen Parameter zu mutieren als auch auf eine Mutation einer äußeren Variablen zu vertrauen, würde ich empfehlen, dass Sie Ihr Programm ändern, um diese zwei schlechten nicht zu verwenden Programmierpraktiken, anstatt auf eine Korrektur zu warten, die nicht erscheint. Entschuldigung für den Fehler.)

Also, um die Frage zu wiederholen:

Der C # -Compiler könnte zum Verschachteln von geschachtelten Lambda-Ausdrücken in eine Ausdrucksbaumstruktur mit Expression.Constant () anstelle von Expression.Quote () und jedem LINQ-Abfrageprovider, der Ausdrucksbäume in eine andere Abfragesprache (wie SQL) verarbeiten möchte, erstellt werden ) könnte nach einer ConstantExpression mit dem Typ Ausdruck anstelle einer UnaryExpression mit dem speziellen Anführungszeichentyp Ausschau halten, und alles andere wäre gleich.

Du hast Recht. Wir könnten semantische Informationen kodieren, die "Schließen-Semantik für diesen Wert induzieren", indem wir den Typ des konstanten Ausdrucks als Flag verwenden .

"Konstante" hätte dann die Bedeutung "Verwenden Sie diesen konstanten Wert, es sei denn, der Typ ist ein Ausdrucksbaumtyp und der Wert ist ein gültiger Ausdrucksbaum. In diesem Fall verwenden Sie stattdessen den Wert, der der Ausdrucksbaum ist, der aus dem Neuschreiben des Inneres des gegebenen Ausdrucksbaums, um eine Schlußsemantik im Kontext irgendeines äußeren Lambdas, in dem wir uns gerade befinden, zu induzieren.

Aber warum sollten wir dieses verrückte Ding machen? Der Anführungszeichen-Operator ist ein irrsinnig komplizierter Operator und sollte explizit verwendet werden, wenn Sie ihn verwenden möchten. Sie schlagen vor, dass wir, um sparsam zu sein, nicht eine zusätzliche Fabrikmethode und einen Knotentyp unter den mehreren Dutzend bereits hinzuzufügen, dass wir Konstanten einen bizarren Eckfall hinzufügen, so dass Konstanten manchmal logische Konstanten sind, und manchmal werden sie neu geschrieben Lambdas mit Verschluss-Semantik.

Es hätte auch den etwas merkwürdigen Effekt, dass Konstante nicht "diesen Wert verwenden" bedeutet. Nehmen wir an, Sie wollten aus einem seltsamen Grund, dass der dritte obige Fall einen Ausdrucksbaum in einen Delegaten kompiliert, der einen Ausdrucksbaum ausgibt, der eine nicht umgeschriebene Referenz auf eine äußere Variable hat? Warum? Vielleicht, weil Sie Ihren Compiler testen und die Konstante einfach weiterreichen möchten, damit Sie später eine andere Analyse durchführen können. Dein Vorschlag würde das unmöglich machen; Jede Konstante, die zufällig vom Ausdrucksbaumtyp ist, würde unabhängig davon neu geschrieben werden. Man hat eine vernünftige Erwartung, dass "konstant" bedeutet "nutze diesen Wert". "Constant" ist ein "mach was ich sage" -Knoten. Die Konstante Prozessor Aufgabe ist nicht zu erraten , was bedeutet , Sie von der Art basiert zu sagen.

Und beachte natürlich, dass du jetzt die Last des Verstehens aufstellst (das heißt, das Verständnis, dass Konstante in jedem Fall eine Semantik kompliziert, die in einem Fall "konstant" bedeutet und auf einem Flag, das im Typsystem ist, "Schließsemantik induzieren") Anbieter, der eine semantische Analyse eines Ausdrucksbaums durchführt, nicht nur bei Microsoft-Anbietern. Wie viele dieser Drittanbieter würden sich irren?

"Zitat" winkt eine große rote Fahne, die sagt: "Hey Kumpel, schau her, ich bin ein verschachtelter Lambda-Ausdruck und ich habe verrückte Semantik, wenn ich über eine äußere Variable geschlossen bin!" während "Constant" sagt: "Ich bin nichts mehr als ein Wert; benutze mich, wie du es für richtig hältst." Wenn etwas kompliziert und gefährlich ist, wollen wir es mit roten Fahnen versehen und diese Tatsache nicht verbergen, indem wir den Benutzer dazu bringen, das Typsystem zu durchforsten , um herauszufinden, ob dieser Wert ein spezieller Wert ist oder nicht.

Darüber hinaus ist die Vorstellung, dass die Vermeidung von Redundanz sogar ein Ziel ist, falsch. Sicher, unnötige, verwirrende Redundanz zu vermeiden ist ein Ziel, aber die meisten Redundanzen sind eine gute Sache. Redundanz schafft Klarheit. Neue Fabrikmethoden und Knotenarten sind billig . Wir können so viele machen, wie wir brauchen, damit jeder eine Operation sauber repräsentiert. Wir müssen nicht auf fiese Tricks zurückgreifen, wie "das bedeutet eine Sache, es sei denn, dieses Feld ist auf dieses Ding eingestellt, in diesem Fall bedeutet es etwas anderes."


Beliebte Antwort

Diese Frage hat bereits eine ausgezeichnete Antwort erhalten. Ich möchte zusätzlich auf eine Ressource hinweisen, die sich bei Fragen zu Expression-Bäumen als hilfreich erweisen kann:

Es gibt ein CodePlex-Projekt von Microsoft namens Dynamic Language Runtime . Seine Dokumentation enthält das Dokument mit dem Titel "Expression Trees v2 Spec" , das genau das ist: Die Spezifikation für LINQ-Ausdrucksbäume in .NET 4.

Zum Beispiel sagt es Folgendes über Expression.Quote :

4.4.42 Zitat

Verwenden Sie Quote in UnaryExpressions, um einen Ausdruck mit einem "konstanten" Wert vom Typ Expression darzustellen. Im Gegensatz zu einem Constant-Knoten behandelt der Quote-Knoten speziell enthaltene ParameterExpression-Knoten. Wenn ein enthaltener ParameterExpression-Knoten ein local deklariert, das im resultierenden Ausdruck geschlossen wird, ersetzt Quote die ParameterExpression an ihren Referenzpositionen. Zur Laufzeit, wenn der Quote-Knoten ausgewertet wird, ersetzt er die Closure-Variablenreferenzen für die ParameterExpression-Referenzknoten und gibt dann den zitierten Ausdruck zurück. [...] (S. 63-64)



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow