Tanz den => Lambda mit mir...

by post@example.com (Admin) Mai 07, 2009 17:55

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.

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 OutOfMemoryException, aber wer will schon so viele Parameter angeben*g*). 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:

(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.

(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..

(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:

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

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

Und mit einem Lambda-Ausdruck gleich noch kürzer

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 es 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 *g*). 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 erst 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. "Expression Trees" werden wahrscheinlich in einer der nächsten Artikel behandelt...

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 ;)

Tags: ,

C#

Kommentare

09.05.2009 13:14:07 #

Mans Heiser

Das ist wirklich ein gut gelungener Überblick. Danke dafür!

Mans Heiser Deutschland |

10.05.2009 13:16:56 #

Wolfgang Kluge

Danke für das Lob ;)

Wolfgang Kluge Deutschland |

17.06.2009 12:13:01 #

Chris

Eine kleine Frage:

mit delegates kann man folgendes machen

z.B. _button.Click += delegate { //DO SOMETHING };

man brauchst sich also nicht unbedingt um die signatur des Eventhandlers zu kümmern.

geht dies auch irgendwie mit der Lamba Notation?

man kann ja _button.Click += (s,e) => { //DO SOMETHING };
was _button.Click += delegate(object sender, EventArgs e) { //DO SOMETHING }

entsprechen würde.
Ich würde gerne die Lambda Notation nutzen, aber wenn ich im Handler weder s, noch e zugreife auch nicht als Parameter deklarieren... Halt so wie delegate {}

Smile

---------------------------------------------------
http://www.christianwirth.de





Chris Deutschland |

18.06.2009 12:42:51 #

Wolfgang Kluge

Bei mir funktioniert das, was Du da geschrieben hast nicht... Der Compiler meckert wegen fehlenden Parametern.

Aber wenn's geht (Version?), dann sollte es auch mit Lambda-Expressions funktionieren. Die müsste dann so aussehen

_button.Click += () => { //DO SOMETHING };


Es kann natürlich sein, dass die delegate-Anweisung (die ja für das Event-System seit der ersten Stunde verantwortlich ist) hier besonders behandelt wird...

Viele Grüße, Wolfgang

Wolfgang Kluge Deutschland |

18.06.2009 16:04:15 #

Wolfgang Kluge

Ups,

da hab ich mich aber verguckt...
Natürlich geht das, was Du geschrieben hast (ich hatte ein Klammernpaar zuviel).

Und ähm, nein. Mir ist keine Lambda-Entsprechung dafür bekannt. Hier wird wirklich die anonyme Methode vom Kompiler aufgebohrt, sobald gar nichts angegeben wird...

Wolfgang Kluge Deutschland |

26.06.2009 15:40:29 #

Christian Wirth

Alles klar, Danke!

Schade Smile

Christian Wirth Deutschland |

30.08.2010 23:01:51 #

trackback

"LINQ und Konsorten" Teil 3 - WIP

"LINQ und Konsorten" Teil 3 - WIP

IT-COW |

Kommentare sind geschlossen

Powered by BlogEngine.NET 1.6.1.6
Theme by Mads Kristensen | Modified by Mooglegiant and me ;)