Expression in LINQ?

1 Antwort

Vom Fragesteller als hilfreich ausgezeichnet

Vorweg:

Da die meisten Leute, die hier Fragen stellen, eher Anfänger sind, gehe ich auch bei dir davon aus: Lambda Expressions sind alles andere als einfach, auch für viele fortgeschrittene Entwickler. Grobes Verständnis und einfache Anwendungsfälle mögen funktionieren, aber sobald Du diese Expressions selber zusammenbauen oder analysieren möchtest, setzt das einiges an Grundwissen und Erfahrung voraus. Z.B. OOP, Reflection und natürlich Delegates sind sehr wichtig, auch ein geübter Umgang mit dem Debugger ist nützlich.

Deine Frage:

Der korrekte Name ist eigentlich LambdaExpression, so heißt auch die Basisklasse von dem Expression<T>-Typ, der im Screenshot genannt wird.
Es wird aber meist mit LINQ zusammen genannt, weil es dafür ursprünglich entwickelt wurde und deshalb noch in dem Namespace ist. Heute gibt's aber einige Anwendungsfälle mehr, da es viele nützliche Möglichkeiten beim Thema Reflection bietet, die den Code deutlich lesbarer machen können. Z.B. wird es für ORM-Mapping (EFCore oder nHibernate) verwendet, oder überall da, wo viel mit Reflection gearbeitet wird.

Diese Expressions sind Objekt-Modelle um C#-Code abbilden zu können, davon gibt's also sehr viele. Die alle kennen muss man aber eher selten, eigentlich nur, wenn man sie selber dynamisch aufbauen oder analysieren möchte.
Danach kann man aus der Expression dann alle Informationen über den Code selber herauslesen. Im Beispiel könnte man also analysieren, dass Du die Age-Property zwei mal brauchst und Du würdest zwei ConstantExpressions mit den Zahlen 12 und 20 finden, dazu noch Expressions für Parameter, Body, Vergleiche, Und-Operator, etc. Z.B. das Entity Frameworks baut daraus dann SQL.

Du schreibst also einen ganz normalen Lambda-Ausdruck, der Compiler erkennt aber, dass es nicht einfach nur normales Lambda sondern ein Lambda-Expression-Objekt sein soll und schreibt dann automatisch den Code, der zur Laufzeit ein Expression-Objekt zusammenbaut. Das kannst Du auch selber machen, ist gar nicht mal so schwer, aber grausig zu lesen und aufwändig.
Das Ergebnis kannst Du dir auch mit Hilfe eines Decompilers anschauen, bedenke aber, dass z.B. ILSpy das erkennt und wieder normalen C#-Code daraus macht, das musst Du dann vorher ausschalten.

Aus der Expression kann man dann alle Metadaten (Stichwort Reflection) herauslesen, man kann also den Code selber analysieren, was ansonsten extrem aufwändig wäre. Oder man kompiliert das ganze und führt es aus, ist aber meist ziemlich unnütz.
Das Zusammenbauen oder analysieren ist aber für die Meisten gar nicht relevant. Framework-Entwickler brauchen das, wenn sie Funktionen damit anbieten wollen, oder ein Anwender davon, wenn eine solche Expression dynamisch sein muss, was aber nur selten vorkommt.
Meistens braucht man nur das, was der Compiler automatisch macht, man ruft eine Methode auf, die eine Expression erwartet, schreibt normales Lambda rein und merkt im Code kaum, welche Komplexität dahinter steckt.

Woher ich das weiß:Berufserfahrung

osion 
Fragesteller
 24.08.2021, 20:15

Also ist es im Beispiel ein Delegate, welches den Parameter Student aufnimmt und ein boolean zurückgibt und statt eine Delegate Funktion zu schreiben, wird eine Lambda expression verwendet?

0
Palladin007  24.08.2021, 20:34
@osion

Das Beispiel im Bild ist nur ein Delegate, da ist nichts von den Expressions, die der Artikel erklären soll, die kommen erst später.

Du musst zwischen Delegate, Lambda-Expression und Lambda-Expression-Tree unterscheiden. Worum es hier geht, ist der Expression-Tree, ich hab's der Einfachheit wegen auch einfach nur Expression genannt. War vielleicht nicht so klug ^^

Das ist eine Lambda-Expression:

s => s.Age > 12 && s.Age < 20

Wenn man das als Delegate nutzt:

Func<Student, bool> isTeenAger = s => s.Age > 12 && s.Age < 20;

// oder:

DoWork(s => s.Age > 12 && s.Age < 20);

void DoWork(Func<Student, bool> isTeenAger) { }

Für einen Delegate kann aber auch eine Methode genutzt werden, oder man kompiliert den Expression-Tree oder man generiert Code zur Laufzeit, etc. Ein Delegate ist also mehr als nur Lambda.

Als Expression-Tree genutzt sieht das dann so aus:

Expression<Func<Student, bool>> isTeenAger = s => s.Age > 12 && s.Age < 20;

// oder:

DoWork(s => s.Age > 12 && s.Age < 20);

void DoWork(Expression<Func<Student, bool>> isTeenAger) { }

Wenn man Letzteres kompiliert, macht der Compiler das daraus:

ParameterExpression parameterExpression = Expression.Parameter(typeof(Student), "s");
Expression<Func<Student, bool>> isTeenAger = Expression.Lambda<Func<Student, bool>>(Expression.AndAlso(Expression.GreaterThan(Expression.Property(parameterExpression, (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/)), Expression.Constant(12, typeof(int))), Expression.LessThan(Expression.Property(parameterExpression, (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/)), Expression.Constant(20, typeof(int)))), new ParameterExpression[1] { parameterExpression });

Das "OpCode not supported: LdMemberToken" meint, dass der Compiler einen IL-OP-Code namens "ldtoken" nutzt, für den es in C# kein Gegenstück gibt, C# unterstützt das einfach nicht. In C# müsste man das dann anders machen, das wäre aber aufwändiger.

Und DAS ist das, worum es in dem Artikel geht, das Objekt-Modell bzw. der Expression-Tree, der da zusammengebaut wird, kann zur Laufzeit wieder analysiert werden.

0
osion 
Fragesteller
 24.08.2021, 21:35
@Palladin007

Also wird ein Baum erstellt, welcher es ermöglicht, z . B. LINQ to SQL zu übersetzen?

0
Palladin007  24.08.2021, 21:39
@osion

Unter Anderem, ja.

Am besten Du probierst es selber aus und schaust dir an, was der Debugger anzeigt.

0