Frage Warum würden Sie Expression > anstelle von Func verwenden?


Ich verstehe Lambdas und die Func und Action Delegierte. Aber Ausdrücke stottern mich. Unter welchen Umständen würdest du einen benutzen? Expression<Func<T>> eher als ein einfaches altes Func<T>?


771
2018-04-27 13:50


Ursprung


Antworten:


Wenn Sie Lambda-Ausdrücke als Ausdrucksbäume behandeln und in sie hineinschauen möchten, anstatt sie auszuführen. Beispiel: LINQ to SQL ruft den Ausdruck ab und konvertiert ihn in die entsprechende SQL-Anweisung und übergibt ihn an den Server (anstatt das Lambda auszuführen).

Konzeptionell Expression<Func<T>> ist ganz anders von Func<T>. Func<T> bezeichnet a delegate Das ist so ziemlich ein Zeiger auf eine Methode und Expression<Func<T>> bezeichnet a Baumdatenstruktur für einen Lambda-Ausdruck. Diese Baumstruktur beschreibt, was ein Lambda-Ausdruck bewirkt anstatt die eigentliche Sache zu machen. Es enthält im Grunde Daten über die Zusammensetzung von Ausdrücken, Variablen, Methodenaufrufen, ... (zum Beispiel enthält es Informationen wie dieses Lambda ist eine Konstante + einige Parameter). Sie können diese Beschreibung verwenden, um sie in eine tatsächliche Methode umzuwandeln (mit Expression.Compile) oder andere Dinge (wie das Beispiel LINQ to SQL) damit machen. Die Behandlung von Lambdas als anonyme Methoden und Ausdrucksbäume ist eine reine Kompilierungszeit.

Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }

wird effektiv 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, ist das was der Compiler erzeugt ganz anders.


956
2018-04-27 13:52



Ich füge eine Antwort für Noobs hinzu, weil diese Antworten über meinem Kopf erschienen, bis ich realisierte, wie einfach es ist. Manchmal ist es deine Erwartung, dass es kompliziert ist, dass du nicht in der Lage bist, deinen Kopf darum zu wickeln.

Ich musste den Unterschied nicht verstehen, bis ich in einen wirklich nervigen "Bug" geriet, der versucht, LINQ-to-SQL generisch zu verwenden:

public IEnumerable<T> Get(Func<T, bool> conditionLambda){
  using(var db = new DbContext()){
    return db.Set<T>.Where(conditionLambda);
  }
}

Das funktionierte großartig, bis ich begann, OutofMemoryExceptions in größeren Datasets zu bekommen. Das Setzen von Haltepunkten innerhalb des Lambda ließ mich erkennen, dass es nacheinander in jeder Zeile in meiner Tabelle nach Übereinstimmungen mit meiner Lambda-Bedingung suchte. Das hat mich eine Weile im Stich gelassen, denn warum zum Teufel behandelt es meine Datentabelle als ein riesiges IEnumerable, anstatt LINQ-to-SQL so zu tun, wie es soll? In meinem LINQ-to-MongoDb-Pendant tat er genau das Gleiche.

Die Lösung bestand einfach darin, sich umzudrehen Func<T, bool> in Expression<Func<T, bool>>, also googelte ich, warum es ein braucht Expression Anstatt von Func, hier enden.

Ein Ausdruck verwandelt einen Delegaten einfach in Daten über sich selbst. Damit a => a + 1 wird so etwas wie "Auf der linken Seite gibt es ein int a. Auf der rechten Seite fügst du 1 hinzu. " Das ist es. Du kannst jetzt nach Hause gehen. Es ist offensichtlich strukturierter als das, aber das ist im Grunde genommen alles, was ein Ausdrucksbaum wirklich ist - nichts, worüber man den Kopf einwickeln könnte.

Wenn man das versteht, wird klar, warum LINQ-to-SQL ein benötigt Expression, und ein Func ist nicht ausreichend. Func bringt nicht einen Weg, um in sich selbst zu gelangen, um das Wesentliche zu sehen, wie man es in eine SQL / MongoDb / andere Abfrage übersetzt. Sie können nicht sehen, ob es bei der Subtraktion Addition oder Multiplikation durchführt. Sie können es nur ausführen. ExpressionAuf der anderen Seite ermöglicht es Ihnen, in den Delegaten zu schauen und alles zu sehen, was er tun möchte, und Sie dazu befähigen, es in das zu übersetzen, was Sie wollen, wie eine SQL-Abfrage. Func funktionierte nicht, weil mein DbContext blind gegenüber dem war, was tatsächlich in dem Lambda-Ausdruck war, um ihn in SQL umzuwandeln, also tat er das Nächstbeste und wiederholte diese Bedingung durch jede Zeile in meiner Tabelle.

Edit: Erläuterung meines letzten Satzes auf John Peters Bitte:

IQueryable erweitert IEnumerable, so IEnumerable Methoden wie Where() Überlastungen erhalten, die akzeptieren Expression. Wenn du ein passierst Expression dazu behalten Sie ein IQueryable als Ergebnis, aber wenn Sie ein übergeben Func, du fällst zurück auf die Basis IEnumerable und du erhältst ein IEnumerable als Ergebnis. Mit anderen Worten, ohne zu bemerken, dass Sie Ihr Dataset in eine Liste umgewandelt haben, die iteriert werden soll, anstatt etwas abzufragen. Es ist schwer einen Unterschied zu bemerken, bis Sie wirklich unter die Haube bei den Signaturen schauen.


196
2018-01-05 08:04



Eine äußerst wichtige Überlegung bei der Wahl von Expression vs Func ist, dass IQueryable-Provider wie LINQ to Entities das, was Sie in einem Ausdruck übergeben, "verdauen" können, aber ignorieren, was Sie in einem Func übergeben. Ich habe zwei Blogbeiträge zum Thema:

Mehr zu Expression vs Func mit Entity Framework und Sich in LINQ verlieben - Teil 7: Expressions und Funcs (der letzte Abschnitt)


91
2018-01-11 15:57



Ich möchte einige Anmerkungen zu den Unterschieden zwischen Func<T> und Expression<Func<T>>:

  • Func<T> ist nur eine normale Old-School-MulticastDelegate;
  • Expression<Func<T>> ist eine Darstellung des Lambda-Ausdrucks in Form eines Ausdrucksbaums;
  • Ausdrucksbaum kann durch Lambda-Ausdruckssyntax oder durch die API-Syntax konstruiert werden;
  • Der Ausdrucksbaum kann zu einem Delegaten kompiliert werden Func<T>;
  • die umgekehrte Konvertierung ist theoretisch möglich, aber es ist eine Art Dekompilierung, es gibt keine eingebaute Funktionalität dafür, da es kein einfacher Prozess ist;
  • Ausdruckbaum kann beobachtet / übersetzt / modifiziert werden 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> gegen Ausdruck <Func <T >>.

Ich hoffe, es wird hilfreich sein.


60
2018-06-11 00:02



Es gibt eine philosophischere Erklärung aus Krzysztof Cwalinas Buch (Framework-Design-Richtlinien: Konventionen, Idiome und Patterns für wiederverwendbare .NET-Bibliotheken);

Rico Mariani

Bearbeiten für Nicht-Image-Version:

Die meisten Male wirst du wollen Funk oder Aktion Wenn alles passieren muss, ist Code auszuführen. Du brauchst Ausdruck wenn der Code analysiert, serialisiert oder optimiert werden muss, bevor er ausgeführt wird. Ausdruck ist zum Nachdenken über Code, Funk / Aktion ist für das Ausführen.


40
2018-03-11 13:10



LINQ ist das kanonische Beispiel (zum Beispiel, um mit einer Datenbank zu sprechen), aber in Wahrheit ist es immer wichtig, etwas auszudrücken Was zu tun, anstatt es tatsächlich zu tun. Zum Beispiel verwende ich diesen Ansatz im RPC-Stack von Protobuf-Netz (um Code-Generierung zu vermeiden etc) - so rufen Sie eine Methode mit:

string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));

Dies dekonstruiert den zu lösenden Ausdrucksbaum SomeMethod (und der Wert jedes Arguments), führt den RPC-Aufruf aus, aktualisiert alle ref/out args und gibt das Ergebnis des Remote-Aufrufs zurück. Dies ist nur über den Ausdrucksbaum möglich. Ich decke das mehr ab Hier.

Ein anderes Beispiel ist, wenn Sie die Ausdrucksbäume manuell erstellen, um sie zu einem Lambda zu kompilieren, wie es von der generische Operatoren Code.


33
2018-04-27 14:13



Sie würden einen Ausdruck verwenden, wenn Sie Ihre Funktion als Daten und nicht als Code behandeln möchten. Sie können dies tun, wenn Sie den Code (als Daten) manipulieren möchten. Wenn Sie keine Notwendigkeit für Ausdrücke sehen, müssen Sie meistens keine verwenden.


18
2018-04-27 13:53



Der Hauptgrund ist, wenn Sie den Code nicht direkt ausführen möchten, sondern ihn lieber untersuchen möchten. Dies kann verschiedene Gründe haben:

  • Zuordnung des Codes zu einer anderen Umgebung (zB C # -Code zu SQL in Entity Framework)
  • Ersetzen von Teilen des Codes in Runtime (dynamische Programmierung oder einfache DRY-Techniken)
  • Code-Validierung (sehr nützlich beim Emulieren von Skripten oder bei der Analyse)
  • Serialisierung - Ausdrücke können relativ einfach und sicher serialisiert werden, Delegierte nicht
  • Stark typisierte Sicherheit für Dinge, die nicht von Natur aus stark typisiert sind, und das Ausnutzen von Compiler-Prüfungen, obwohl Sie dynamische Aufrufe in Laufzeit ausführen (ASP.NET MVC 5 mit Razor ist ein schönes Beispiel)

15
2018-03-26 12:54