Tanz den => Lambda mit mir...

Wer sich noch erfolgreich gegen die zugegebenermaßen ungewöhnlich anmutende Schreibweise der Lambda-Ausdrücke in .NET wehrt, dem kann nur gesagt werden: so schlimm wie es aussieht ist’s bei weitem nicht! Und einmal verinnerlicht ist’s wie Fahrradfahren. Warum man sich daran gewöhnen sollte? Weil man es kann ;)

Allgemein

Lambda(λ)-Ausdrücke gehören irgendwie zu den anonymen Methoden. Allerdings hat man sie nicht umsonst Ausdruck und nicht Methode genannt. Sie sind also nur ähnlich. Sie können sich aber exakt genauso verhalten wie anonyme Methoden. Dazu müssen sie lediglich einem delegate zugewiesen werden. Grob gesagt sind sie eine Weiterentwicklung der anonymen Methoden.

Alle anonyme Methoden können nicht direkt aufgerufen werden. Ihnen fehlt schlicht und recht wenig ergreifend der entsprechende Name (auch wenn intern wieder einer erzeugt wird) ;) Damit man weniger schreiben muss, erkennt der Compiler anhand des Kontextes (der Zuweisung) die Parametertypen und den Typ des Rückgabewertes (falls vorhanden). Bei “normalen” anonymen Methoden (die mit dem delegate-Keyword) wird nur der Rückgabetyp anhand des Kontextes ermittelt.

Die Lambda-Expressions selbst bestehen dabei nur aus 3 Teilen, wobei einer davon (der Lambda-Operator =>) fest ist und lediglich die beiden anderen Teile voneinander trennt.

1
_Argumentliste_ => _(Ausdruck | Anweisungsblock)_

=> wird “goes to“ gesprochen. Wie man das in Deutsch ausdrücken will/soll/muss - ich weiß ich nicht.

Argumentliste

Die Argumentliste kann 0, 1 oder (fast) beliebig vielen Elementen enthalten (bei mir kommt es ab 4694 Parametern zu einer OutOfMemoryException, aber wer will schon so viele Parameter angeben). Die einzelnen Argumente müssen dabei in Klammern geschrieben werden, es sei denn, es gibt nur ein Argument. Dann gehts auch ohne Klammer. Also ist z.B. folgendes gültig:

1
2
3
4
(a) => ...
a => ... // 1 einzelner Parameter kann auch ohne Klammern angegeben werden
() => ...
(a, b) => ...

Der Compiler ermittelt die Parametertypen aus dem Kontext. Wenn man dies “zulässt”, nennt man das “implizite Typisierung”.

Wenn man will (oder es nötig sein sollte), kann man auch den Typ der einzelnen Argumente angeben (nennt sich folgerichtig “explizit Typisierung”). Damit sieht es mehr nach “gewohnter” Parameterliste aus. In diesem Fall müssen auch immer Klammern gesetzt werden. Es gelten die gleichen Einschränkungen wie bei anonymen Methoden, es darf also z.B. kein params - Array verwendet werden.

1
2
(string a) => ...
(string a, int b) => ...

Wenn man explizit typisieren will, dann muss man dies mit allem Parametern tun.

Ausdruck, Anweisungsblock

Auf der rechten Seite des Lambda-Operators steht die eigentlich Methode. Und auch hier wurde viel getan, damit man nicht viel schreiben muss. Ein Anweisungsblock kann aus keiner, einer oder mehreren Anweisungen bestehen. Diese werden in geschweifte Klammern geschrieben. Wie eine ganz normale Methode eben.

Wenn der Anweisungsblock aus nur einem Ausdruck besteht (z.B. aus nur einer return-Anweisung), dann kann man die geschweiften Klammern auch weglassen. Je nach Kontext ist das was übrig bleibt dann entweder der Rückgabewert der Funktion, oder (wenn das delegate so definiert ist, dass kein Rückgabewert erwaret wird) die einzige Zeile.

Wenn man gar nichts tun will, dann muss ein leeres Klammernpaar geschrieben werden. Anhand eines kleinen Beispiels ist das aber wie immer leichter erklärt..

1
2
3
4
(int a) => { return a * 2; }; // Anweisungsblock
(int a) => a * 2; // einzelner Ausdruck
(int a) => { }; // leerer Anweisungsblock.

Die ersten beiden Codezeilen sind absolut gleichbedeutend. Ein leerer Anweisungsblock generiert eine Funktion ohne Rückgabewert (void). Wenn das delegate, dem der Lambda-Ausdruck zugeordnet wird keinen Rückgabewert erwartet und man nur eine Anweisung hat, kann man ebenfalls die geschweiften Klammern weglassen (siehe nächstes Beispiel).

Anwendung

Nun zu einer praktischen Anwendung. Denn ohne das außenrum sieht es ja nicht sonderlich komplex aus. Die Lamba-Expressions können nämlich nicht so ohne weiteres irgendwohin “gestellt” werden. Sie müssen einer Variable zugewiesen werden. Am Beispiel des Page.OnLoad-Events gibt es (mindestens) 3 Möglichkeiten.

Mittels EventHandler-delegate:

1
2
3
4
5
6
Page.Init += new EventHandler(Page_Init);
// ...
void Page_Init(object sender, EventArgs e) {
this.Trace.Warn("Page_Init");
}

Mit einer anonymen Methode kann dies schon kürzer geschrieben werden

1
2
3
Page.Init += delegate(object sender, EventArgs e) {
this.Trace.Warn("Page_Init");
};

Und mit einem Lambda-Ausdruck gleich noch kürzer

1
Page.Init += ( sender, e ) => this.Trace.Warn( "Page_Init" );

Auch hier kann man der Lambda-Ausdruck ohne Klammern schreiben. Damit spart man wiederum etwas Code aber im Grunde sind die Unterschiede zwischen anonymer Methode und Lambda-Ausdruck kaum erwähnenswert.

Was macht man also damit…

…ist eine mehr als berechtigte Frage. Wenn man “nur” etwas Code damit spart, dann kann man sich das “Erlernen” auch sparen (und das Konstrukt gleich auch der Sprache C# ersparen) und sich lieber auf wichtigeres konzentrieren. Lambda-Ausdrücke, die delegate-Variablen zugewiesen werden sind de fakto nichts anderes als anonyme Methoden die mittels delegate-Keyword erstellt wurden (sagt ILDisasm). Wozu also der Stress? Wo bleibt die “Weiterentwicklung”…

Nun, die Lambda-Ausdrücke haben Einzug in C# (und VB.NET) durch LINQ erhalten. Anonyme Methoden alleine waren nicht mehr ausreichend. Denn man kann Lambda-Ausdrücke wunderbar einfach auch zur Laufzeit analysieren oder sogar erst aufbauen. Um das zu erreichen, weist man einen Lambda-Ausdruck nicht einem delegate, sondern einer Variable vom Typ System.Linq.Expressions.Expression<TDelegate> zu. Heraus kommt ein so genannter “Expression Tree“. LINQ wird erst durch die Analyse der Ausdrücke ermöglicht, denn damit kann z.B. im Falle von LINQ to SQL aus einem Lambda-Ausdruck ein echter SQL-Befehl werden.

Bei den Expression Trees gibt es allerdings eine wichtige Einschränkungen was den Inhalt der Lambda-Expression betrifft - dieser darf nur aus einem einzelnen Ausdruck und nicht aus einem Anweisungsblock bestehen. Doch das sprengt jetzt hier den Rahmen des Artikels.

Wozu nun Lambda-Ausdrücke, wenn man keinen LINQ to irgendwas - Provider bauen will und einem auch sonst keine Verwendungsmöglichkeit von Expression Trees einfällt. Ich kann es nicht beantworten: weils schön und kürzer ist oder vielleicht auch einfach nur - wie bereits eingangs erwähnt - weil man es kann ;)