Ich verstehe Lambdas und die Func
und Action
Delegierten. Aber Ausdrücke überraschen mich.
Unter welchen Umständen würden Sie Expression<Func<T>>
eher eine als eine einfache alte verwenden Func<T>
?
Ich verstehe Lambdas und die Func
und Action
Delegierten. Aber Ausdrücke überraschen mich.
Unter welchen Umständen würden Sie Expression<Func<T>>
eher eine als eine einfache alte verwenden Func<T>
?
Antworten:
Wenn Sie Lambda-Ausdrücke als Ausdrucksbäume behandeln und in sie schauen möchten, anstatt sie auszuführen. Beispielsweise ruft LINQ to SQL den Ausdruck ab und konvertiert ihn in die entsprechende SQL-Anweisung und sendet ihn an den Server (anstatt das Lambda auszuführen).
Konzeptionell Expression<Func<T>>
ist völlig anders als Func<T>
. Func<T>
bezeichnet a, delegate
das so ziemlich ein Zeiger auf eine Methode ist, und Expression<Func<T>>
bezeichnet eine Baumdatenstruktur für einen Lambda-Ausdruck. Diese Baumstruktur beschreibt, was ein Lambda-Ausdruck tut, anstatt das eigentliche zu tun. Es enthält im Wesentlichen Daten über die Zusammensetzung von Ausdrücken, Variablen, Methodenaufrufen usw. (zum Beispiel enthält es Informationen wie dieses Lambda ist eine Konstante + ein Parameter). Sie können diese Beschreibung verwenden, um sie in eine tatsächliche Methode (mit Expression.Compile
) zu konvertieren oder andere Dinge (wie das Beispiel LINQ to SQL) damit zu tun. Die Behandlung von Lambdas als anonyme Methoden und Ausdrucksbäume ist eine reine Sache zur 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:
Während beide zur Kompilierungszeit gleich aussehen, ist das, was der Compiler generiert, völlig anders .
Expression
enthält die Metainformationen zu einem bestimmten Delegaten.
Expression<Func<...>>
statt nur verwenden Func<...>
.
(isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }
solcher Ausdruck ist ein ExpressionTree. Für die If-Anweisung werden Verzweigungen erstellt.
Ich füge eine Antwort für Noobs hinzu, weil diese Antworten über meinem Kopf schienen, bis mir klar wurde, wie einfach es ist. Manchmal ist es Ihre Erwartung, dass es kompliziert ist, dass Sie nicht in der Lage sind, Ihren Kopf darum zu wickeln.
Ich musste den Unterschied nicht verstehen, bis ich auf einen wirklich nervigen "Fehler" stieß, der versuchte, 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);
}
}
Dies funktionierte hervorragend, bis ich anfing, OutofMemoryExceptions für größere Datensätze zu erhalten. Durch das Setzen von Haltepunkten innerhalb des Lambda wurde mir klar, dass es nacheinander durch jede Zeile in meiner Tabelle iterierte, um nach Übereinstimmungen mit meinem Lambda-Zustand zu suchen. Das hat mich eine Weile verblüfft, denn warum zum Teufel behandelt es meine Datentabelle als eine riesige IEnumerable, anstatt LINQ-to-SQL so zu machen, wie es soll? Genau das Gleiche tat es auch in meinem LINQ-to-MongoDb-Gegenstück.
Das Update war einfach zu drehen Func<T, bool>
in Expression<Func<T, bool>>
, so dass ich gegoogelt , warum es eine braucht Expression
statt Func
, endet hier oben.
Ein Ausdruck verwandelt einen Delegaten einfach in Daten über sich. So a => a + 1
wird so etwas wie "Auf der linken Seite gibt es eine int a
. Auf der rechten Seite addieren Sie 1 dazu." Das ist es. Du kannst jetzt nach Hause gehen. Es ist offensichtlich strukturierter als das, aber das ist im Wesentlichen alles, was ein Ausdrucksbaum wirklich ist - nichts, um den man den Kopf wickeln könnte.
Wenn man das versteht, wird klar, warum LINQ-to-SQL eine benötigt Expression
und eine Func
nicht ausreicht. Func
bringt keine Möglichkeit mit sich, in sich selbst hineinzukommen, um zu sehen, wie man es in eine SQL / MongoDb / andere Abfrage übersetzt. Sie können nicht sehen, ob es sich um Addition, Multiplikation oder Subtraktion handelt. Alles was Sie tun können, ist es auszuführen. Expression
Auf der anderen Seite können Sie in den Delegaten schauen und alles sehen, was er tun möchte. Auf diese Weise können Sie den Delegaten in eine beliebige SQL-Abfrage übersetzen. Func
hat nicht funktioniert, weil mein DbContext für den Inhalt des Lambda-Ausdrucks blind war. Aus diesem Grund konnte der Lambda-Ausdruck nicht in SQL umgewandelt werden. Es hat jedoch das nächstbeste getan und diese Bedingung durch jede Zeile in meiner Tabelle wiederholt.
Edit: auf Wunsch von John Peter meinen letzten Satz erläutern:
IQueryable erweitert IEnumerable, sodass IEnumerables Methoden wie Where()
das Erhalten akzeptabler Überladungen Expression
. Wenn Sie eine Expression
an diese übergeben, behalten Sie als Ergebnis eine IQueryable bei, aber wenn Sie eine übergeben Func
, greifen Sie auf die Basis-IEnumerable zurück und erhalten als Ergebnis eine IEnumerable. Mit anderen Worten, ohne es zu bemerken, haben Sie Ihr Dataset in eine Liste umgewandelt, die iteriert werden soll, anstatt etwas abzufragen. Es ist schwer, einen Unterschied zu bemerken, bis man die Unterschriften wirklich unter die Haube schaut.
Eine äußerst wichtige Überlegung bei der Auswahl von Expression vs Func ist, dass IQueryable-Anbieter wie LINQ to Entities das, was Sie in einem Expression übergeben, "verdauen" können, aber ignorieren, was Sie in einem Func übergeben. Ich habe zwei Blog-Beiträge zu diesem Thema:
Mehr zu Expression vs Func mit Entity Framework und Verlieben in LINQ - Teil 7: Ausdrücke und Funktionen (letzter Abschnitt)
Ich möchte einige Anmerkungen zu den Unterschieden zwischen Func<T>
und hinzufügen Expression<Func<T>>
:
Func<T>
ist nur 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. Expression <Func <T>> .
Hoffe es wird hilfreich sein.
Es gibt eine philosophischere Erklärung dafür aus Krzysztof Cwalinas Buch ( Framework Design Guidelines: Konventionen, Redewendungen und Muster für wiederverwendbare .NET-Bibliotheken );
Bearbeiten für Nicht-Bildversion:
In den meisten Fällen möchten Sie Func oder Action, wenn nur Code ausgeführt werden muss. Sie benötigen Expression, wenn der Code analysiert, serialisiert oder optimiert werden muss, bevor er ausgeführt wird. Ausdruck dient zum Nachdenken über Code, Func / Action zum Ausführen.
database.data.Where(i => i.Id > 0)
ausgeführt werden als SELECT FROM [data] WHERE [id] > 0
. Wenn Sie nur in einem Func passieren, haben Sie Scheuklappen auf Ihrem Fahrer setzen und alles was man tun kann , ist , SELECT *
und dann , wenn es all diese Daten in den Speicher geladen wird , durchlaufen jede und ausfiltern alles mit id> 0. Ihre Verpackung Func
in Expression
ermächtigt der Treiber, um das zu analysieren Func
und es in eine SQL / MongoDb / andere Abfrage umzuwandeln.
Expression
aber wenn ich im Urlaub bin, wird es sein Func/Action
;)
LINQ ist das kanonische Beispiel (zum Beispiel das Sprechen mit einer Datenbank), aber in Wahrheit geht es Ihnen immer mehr darum, auszudrücken, was zu tun ist, als es tatsächlich zu tun. Zum Beispiel verwende ich diesen Ansatz im RPC-Stapel von protobuf-net (um Codegenerierung usw. zu vermeiden) - also rufen Sie eine Methode auf mit:
string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
Dadurch wird der aufzulösende Ausdrucksbaum SomeMethod
(und der Wert jedes Arguments) dekonstruiert , der RPC-Aufruf ausgeführt, any ref
/ out
args aktualisiert und das Ergebnis des Remote-Aufrufs zurückgegeben. Dies ist nur über den Ausdrucksbaum möglich. Ich beschreibe das hier mehr .
Ein anderes Beispiel ist, wenn Sie die Ausdrucksbäume manuell erstellen, um sie zu einem Lambda zu kompilieren, wie dies durch den Code der generischen Operatoren erfolgt .
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) bearbeiten möchten. Wenn Sie keinen Bedarf an Ausdrücken sehen, müssen Sie wahrscheinlich keinen verwenden.
Der Hauptgrund ist, wenn Sie den Code nicht direkt ausführen, sondern überprüfen möchten. Dies kann verschiedene Gründe haben:
Expression
kann es genauso unmöglich sein, ein zu delegieren wie ein Delegat, da jeder Ausdruck einen Aufruf einer beliebigen Delegaten- / Methodenreferenz enthalten kann. "Einfach" ist natürlich relativ.
Ich sehe noch keine Antworten, die die Leistung erwähnen. Es ist schlecht, Func<>
s zu übergeben Where()
oder Count()
. Wirklich schlecht. Wenn Sie a verwenden, wird stattdessen Func<>
das IEnumerable
LINQ-Zeug aufgerufen IQueryable
, was bedeutet, dass ganze Tabellen eingezogen und dann gefiltert werden. Expression<Func<>>
ist erheblich schneller, insbesondere wenn Sie eine Datenbank abfragen, auf der sich ein anderer Server befindet.