Ich verstehe Lambdas und die Func
et Action
Delegierte. Aber Ausdrücke verblüffen mich.
Unter welchen Umständen würden Sie eine Expression<Func<T>>
als eine einfache alte Func<T>
?
Ich verstehe Lambdas und die Func
et Action
Delegierte. Aber Ausdrücke verblüffen mich.
Unter welchen Umständen würden Sie eine Expression<Func<T>>
als eine einfache alte Func<T>
?
Wenn Sie Lambda-Ausdrücke als Ausdrucksbäume behandeln und in sie hineinschauen wollen, anstatt sie auszuführen. Zum Beispiel holt LINQ to SQL den Ausdruck ab, konvertiert ihn in die entsprechende SQL-Anweisung und übergibt ihn an den Server (anstatt den Lambda-Ausdruck auszuführen).
Konzeptionell, Expression<Func<T>>
es völlig anders von Func<T>
. Func<T>
kennzeichnet eine delegate
was in etwa ein Zeiger auf eine Methode ist und Expression<Func<T>>
kennzeichnet eine Baumdatenstruktur für einen Lambda-Ausdruck. Diese Baumstruktur beschreibt, was ein Lambda-Ausdruck bewirkt anstatt die eigentliche Sache zu tun. Sie enthält im Wesentlichen Daten über die Zusammensetzung von Ausdrücken, Variablen, Methodenaufrufen usw. (z. B. enthält sie Informationen wie "Dieses Lambda ist eine Konstante + ein Parameter"). Sie können diese Beschreibung verwenden, um sie in eine tatsächliche Methode umzuwandeln (mit Expression.Compile
) oder andere Dinge (wie das LINQ to SQL-Beispiel) mit ihm machen. Die Behandlung von Lambdas als anonyme Methoden und Ausdrucksbäume ist eine reine Kompilierzeitsache.
Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
wird zu einer IL-Methode kompiliert, die nichts erhält und 10 zurückgibt.
Expression<Func<int>> myExpression = () => 10;
wird in eine Datenstruktur konvertiert, die einen Ausdruck beschreibt, der keine Parameter erhält und den Wert 10 zurückgibt:
Während beide zur Kompilierzeit gleich aussehen, erzeugt der Compiler Folgendes völlig unterschiedlich .
Ich füge eine Antwort für Anfänger hinzu, denn diese Antworten schienen mir zu hoch, bis ich merkte, wie einfach es ist. Manchmal ist es die Erwartung, dass es kompliziert ist, die dazu führt, dass man nicht in der Lage ist, es zu "verstehen".
Ich brauchte den Unterschied nicht zu verstehen, bis ich beim Versuch, LINQ-to-SQL generisch zu verwenden, auf einen wirklich ärgerlichen "Bug" stieß:
public IEnumerable<T> Get(Func<T, bool> conditionLambda){
using(var db = new DbContext()){
return db.Set<T>.Where(conditionLambda);
}
}
Dies funktionierte gut, bis ich anfing, OutofMemoryExceptions auf größere Datensätze zu erhalten. Festlegen von Haltepunkten innerhalb der Lambda machte mir klar, dass es durch jede Zeile in meiner Tabelle eine nach der anderen auf der Suche nach Übereinstimmungen mit meiner Lambda-Bedingung durchlaufen wurde. Das verwirrte mich eine Weile, denn warum zum Teufel behandelt es meine Datentabelle als eine riesige IEnumerable, anstatt LINQ-to-SQL zu machen, wie es eigentlich sollte? Es war auch tun genau das gleiche in meinem LINQ-to-MongoDb Gegenstück.
Die Lösung bestand einfach darin, die Func<T, bool>
en Expression<Func<T, bool>>
also habe ich gegoogelt, warum es eine Expression
代わりに Func
und landet hier.
Ein Ausdruck verwandelt einen Delegaten einfach in Daten über sich selbst. Also a => a + 1
wird zu etwas wie "Auf der linken Seite befindet sich ein int a
. Auf der rechten Seite addieren Sie 1 dazu." Das war's. Sie können jetzt nach Hause gehen. Es ist natürlich mehr strukturiert als das, aber das ist im Grunde alles, was ein Ausdruck Baum wirklich ist - nichts, um Ihren Kopf zu wickeln.
Wenn man das versteht, wird klar, warum LINQ-to-SQL eine Expression
und eine Func
ist nicht ausreichend. Func
enthält keine Möglichkeit, in sich selbst hineinzugehen, um zu sehen, wie man sie in eine SQL/MongoDb/andere Abfrage übersetzt. Sie können nicht sehen, ob es eine Addition, Multiplikation oder Subtraktion durchführt. Alles, was Sie tun können, ist, sie auszuführen. Expression
ermöglicht es Ihnen hingegen, in den Delegierten hineinzuschauen und alles zu sehen, was er tun will. Dadurch können Sie den Delegaten in alles übersetzen, was Sie wollen, z. B. in eine SQL-Abfrage. Func
funktionierte nicht, weil mein DbContext für den Inhalt des Lambda-Ausdrucks blind war. Aus diesem Grund konnte es den Lambda-Ausdruck nicht in SQL umwandeln, aber es tat das Nächstbeste und iterierte die Bedingung durch jede Zeile in meiner Tabelle.
Edit: Ich habe meinen letzten Satz auf Wunsch von John Peter noch einmal erläutert:
IQueryable erweitert IEnumerable, so dass die Methoden von IEnumerable wie Where()
Überladungen erhalten, die Folgendes akzeptieren Expression
. Wenn Sie eine Expression
dazu, erhalten Sie eine IQueryable als Ergebnis, aber wenn Sie eine Func
, greifen Sie auf die Basis IEnumerable zurück und erhalten als Ergebnis eine IEnumerable. Mit anderen Worten: Ohne es zu merken, haben Sie Ihren Datensatz in eine Liste verwandelt, die iteriert werden soll, anstatt etwas abzufragen. Es ist schwer, einen Unterschied zu bemerken, bis Sie wirklich unter der Haube auf die Signaturen schauen.
Eine eher philosophische Erklärung dazu findet sich im Buch von Krzysztof Cwalina ( Rahmengestaltungsrichtlinien: Konventionen, Redewendungen und Muster für wiederverwendbare .NET-Bibliotheken );
Bearbeiten für Nicht-Bild-Version:
In den meisten Fällen werden Sie Folgendes wollen Func ou Aktion wenn es nur darum geht, einen Code auszuführen. Sie brauchen Ausdruck wenn der Code analysiert, serialisiert oder optimiert werden muss, bevor er ausgeführt wird. Ausdruck ist zum Nachdenken über Code da, Funktion/Aktion ist für den Betrieb.
Eine äußerst wichtige Überlegung bei der Wahl zwischen Expression und Func ist, dass IQueryable-Anbieter wie LINQ to Entities das, was Sie in einer Expression übergeben, "verdauen" können, aber das, was Sie in einer Func übergeben, ignorieren. Ich habe zwei Blogbeiträge zu diesem Thema:
Mehr über Expression vs. Func mit Entity Framework et Verliebt in LINQ - Teil 7: Ausdrücke und Funcs (der letzte Abschnitt)
Ich möchte noch einige Anmerkungen zu den Unterschieden zwischen Func<T>
et Expression<Func<T>>
:
Func<T>
ist ein normales MulticastDelegate der alten Schule;Expression<Func<T>>
ist eine Darstellung des Lambda-Ausdrucks in Form eines Ausdrucksbaums;Func<T>
;ExpressionVisitor
;Func<T>
;Expression<Func<T>>
.Es gibt einen Artikel, der die Details mit Codebeispielen beschreibt:
LINQ: Func<T> vs. Ausdruck<Func<T>> .
Ich hoffe, es wird hilfreich sein.
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.