1153 Stimmen

Warum sollten Sie Expression<Func<T>> und nicht Func<T> verwenden?

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> ?

1326voto

mmx Punkte 400975

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:

Expression vs Func größeres Bild

Während beide zur Kompilierzeit gleich aussehen, erzeugt der Compiler Folgendes völlig unterschiedlich .

485voto

Chad Hedgcock Punkte 10251

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.

122voto

Oğuzhan Soykan Punkte 2332

Eine eher philosophische Erklärung dazu findet sich im Buch von Krzysztof Cwalina ( Rahmengestaltungsrichtlinien: Konventionen, Redewendungen und Muster für wiederverwendbare .NET-Bibliotheken );

Rico Mariani

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.

117voto

LSpencer777 Punkte 1729

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)

89voto

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;
  • Ausdrucksbaum kann durch die Lambda-Ausdruckssyntax oder durch die API-Syntax konstruiert werden;
  • Ausdrucksbaum kann zu einem Delegaten kompiliert werden Func<T> ;
  • die umgekehrte Konvertierung ist theoretisch möglich, aber es handelt sich um eine Art Dekompilierung, für die es keine eingebaute Funktionalität gibt, da es sich nicht um einen einfachen Prozess handelt;
  • Expressionsbaum kann durch die ExpressionVisitor ;
  • die Erweiterungsmethoden für IEnumerable arbeiten mit Func<T> ;
  • die Erweiterungsmethoden für IQueryable arbeiten mit 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.com

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.

Powered by:

X