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>
?
c#
delegates
lambda
expression-trees
Richard Nagle
quelle
quelle
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 alsFunc<T>
.Func<T>
bezeichnet a,delegate
das so ziemlich ein Zeiger auf eine Methode ist, undExpression<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 (mitExpression.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.wird effektiv zu einer IL-Methode kompiliert, die nichts erhält und 10 zurückgibt.
wird in eine Datenstruktur konvertiert, die einen Ausdruck beschreibt, der keine Parameter erhält und den Wert 10 zurückgibt:
größeres Bild
Während beide zur Kompilierungszeit gleich aussehen, ist das, was der Compiler generiert, völlig anders .
quelle
Expression
enthält die Metainformationen zu einem bestimmten Delegaten.Expression<Func<...>>
statt nur verwendenFunc<...>
.(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:
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>
inExpression<Func<T, bool>>
, so dass ich gegoogelt , warum es eine brauchtExpression
stattFunc
, 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 eineint 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 eineFunc
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 ÜberladungenExpression
. Wenn Sie eineExpression
an diese übergeben, behalten Sie als Ergebnis eine IQueryable bei, aber wenn Sie eine übergebenFunc
, 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.quelle
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)
quelle
Ich möchte einige Anmerkungen zu den Unterschieden zwischen
Func<T>
und hinzufügenExpression<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.
quelle
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:
quelle
database.data.Where(i => i.Id > 0)
ausgeführt werden alsSELECT 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 VerpackungFunc
inExpression
ermächtigt der Treiber, um das zu analysierenFunc
und es in eine SQL / MongoDb / andere Abfrage umzuwandeln.Expression
aber wenn ich im Urlaub bin, wird es seinFunc/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:
Dadurch wird der aufzulösende Ausdrucksbaum
SomeMethod
(und der Wert jedes Arguments) dekonstruiert , der RPC-Aufruf ausgeführt, anyref
/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 .
quelle
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.
quelle
Der Hauptgrund ist, wenn Sie den Code nicht direkt ausführen, sondern überprüfen möchten. Dies kann verschiedene Gründe haben:
quelle
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 übergebenWhere()
oderCount()
. Wirklich schlecht. Wenn Sie a verwenden, wird stattdessenFunc<>
dasIEnumerable
LINQ-Zeug aufgerufenIQueryable
, 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.quelle