C#: Grundlagen zu Delegaten, anonymen Methoden und Lambda-Ausdrücken

Bei den Begriffen Delegate, anonyme Methode oder Lambda-Ausdruck kommt es manchmal zu Verwirrungen. Aus technischer Sicht sind diese Konstrukte aber eng miteinander verbunden und können einem Entwickler einiges an Codierungsaufwand sparen bzw. ermöglichen erst die elegante Lösung von „komplexen“ Sachverhalten. Einige moderne Konzepte von C# basieren auf dieser technischen Basis, wie z.B. LINQ, Events, usw. Der Artikel versucht nun, die einzelnen Konstrukte näher zu beschreiben und diese anhand von Beispielen zu verdeutlichen.

Delegate

Grundsätzlich ist ein Delegate ein Typ wie jeder andere auch, mit dem Unterschied, dass er nicht auf ein „gewöhnliches“ Objekt zeigt, sondern auf eine Methode verweist (z.B. sind Ereignishandler nichts weiter als Methoden, die durch Delegaten aufgerufen werden). Man könnte einen Delegaten also mit einem C/C++-Funktionszeiger vergleichen. Aus technischer Sicht existieren aber ein paar grundlegende Unterschiede. Im Gegensatz zu einem C/C++-Funktionszeiger ist ein Delegat ein objektorientiertes und typsicheres Konstrukt. Ein Delegat-Typ wird dabei wie folgt deklariert:

Hier wird zunächst der Typ erzeugt. Die Deklaration enthält das Schlüsselwort delegate, den Rückgabetyp, den Methodenbezeichner sowie die Parameterliste für die Methode. Jetzt benötigen wir noch eine entsprechende Methode, auf die der Delegat verweisen soll. Eine passende Methode könnte daher wie folgt aussehen:

Wie oben zu sehen hat die gezeigte Methode einen Rückgabewert vom Typ int und erwartet zwei Parameter ebenfalls vom Typ int. Jetzt muss dem Delegat nur noch die Methode zugwiesen werden:

Der Aufrufe des Delegate sieht dann wie folgt aus:

Mit einem Delegat ist es also möglich die aufzurufende Methode dynamisch zu gestalten. Jede Methode einer beliebigen verfügbaren Klasse oder Struktur, die mit dem Delegattyp übereinstimmt, kann dem Delegaten zugewiesen werden. Damit können z.B. sogenannte Plugin-Methoden realisiert werden. Es wäre ebenfalls denkbar eine weitere Methode für die Multiplikation zu implementieren und diese dem Delegate zuzuweisen.

Ein weiteres Beispiel: Man stelle sich eine einfache Anwendung vor, die einfach nur eine Menge von Werten sortiert. Standardmäßig implementiert die Anwendung den BubbleSort-Algorithmus. Es gibt aber durchaus effizientere Algorithmen für die Sortierung, wie QuickSort, SelectionSort, usw. Jetzt könnte man die einzelnen Sortieralgorithmen in einer eigenen Methode (sozusagen als Plugin) implementieren und diese einem entsprechenden Delegate zuweisen. Das Hauptprogramm müsste somit nicht angepasst werden, da dort immer nur der Delegate angesprochen wird. Welcher Sortieralgorithmus angewendet wird ist für die das Hauptprogramm damit irrelevant!

In dem oben gezeigten Beispiel wird dem Delegaten Calculate die benannte Methode Add zugewiesen (benannte Methode deshalb, weil die Methode einen Namen hat). Neben den benannten Methoden gibt es in C# noch unbenannte bzw. anonyme Methoden.

Anonyme Methoden

In Version 2.0 von C# wurden anonyme Methoden eingeführt. Anonyme Methoden zeichnen sich dadurch aus, dass sie keinen Namen haben und bieten im Wesentlichen die Möglichkeit, einen Codeblock bzw. den Methodenköroper direkt bei der Delegate-Instanziierung anzugeben, was die Instanziierung eines Delegaten vereinfacht. Der Code ist somit nicht mehr namentlich mit einem Methodenbezeichner verbunden und wird deshalb als „anonyme“ Methode bezeichnet. Somit kann man die oben gezeigte Zuweisung des Delegaten auch wie folgt schreiben:

Das Schlüsselwort delegate dient dazu, einen Delegate zu instanziieren und das Objekt direkt mit einer anonymen Methode zu verbinden. Hinter delegate ist die Parameterliste entsprechend der Delegate-Definition angegeben. Handelt es sich um eine parameterlose, anonyme Methode, bleibt die Liste leer und man kann auf die Angabe der runden Klammern verzichten. Anonyme Methoden erlauben es also, auf das explizite Deklarieren der Methode zu verzichten und stattdessen den Programmcode in Verbindung mit einem passenden Delegaten direkt zuzuweisen. Im oben gezeigten Fall werden dem Delegaten noch die zwei Parameter vom Typ int mitgegeben. Die Parameterliste einer anonymen Methode kann aber auch leer sein. Dazu mal das folgende Beispiel:

An diesem Beispiel ist schön zu sehen wie man durch eine anonyme Methode den Codierungsaufwand reduzieren kann. Hier wird ein neuer Thread erstellt und der Thread erhält direkt den Code, der ausgeführt werden soll, ohne dass eine zusätzliche Methode für den Delegaten erstellt werden muss. Ein weiteres Einsatzgebiet ergeben sich bei Ereignisbehandlungen (Event-Handler):

Das obige Beispiel kann mit einer anonymen Methode wie folgt umgeschrieben werden:

In diesem Beispiel werden die übergebenen Parameter nicht verwendet und deshalb könnte man die Parameter auch weglassen. Das würde dann so aussehen:

Diese Schreibweise hat jetzt allerdings den folgenden Nachteil: Soll die Methode für die Ereignisbehandlung mehrfach verwendet ist dies nicht möglich. Hier muss die „traditionelle“ Schreibweise verwendet werden.

Hinweis

Der Gültigkeitsbereich der Parameter einer anonymen Methode ist der anonyme Methodenblock. Die lokalen Variablen und Parameter, in deren Gültigkeitsbereich eine anonyme Methodendeklaration enthalten ist, werden als äußere Variablen der anonymen Methode bezeichnet. Zum Beispiel ist n im folgenden Codesegment eine äußere Variable:

Da sich der Anweisungsblock einer anonymen Methode immer innerhalb einer äußeren Methode befindet, kann aus der anonymen Methode heraus auf jede andere Variable der äußeren Methode zugegriffen werden.

Lambda-Ausdruck

Lambda-Ausdrücke gehören seit C#-Version 3.0 zum Sprachumfang und sind eigentlich nichts anderes als funktional erweiterte anonyme Methoden. Zum Erstellen eines Lambda-Ausdrucks werden die Eingabeparameter (falls vorhanden) auf der linken Seite des Lambda-Operators => angegeben und der Ausdruck oder der Anweisungsblock steht auf der rechten Seite. Die Syntax sieht also wie folgt aus:

(Inputparameter) => {Expression/Statements}

Somit könnte man das bereits gezeigte Beispiel auch so schreiben:

Wie zu sehen ist dieser Lamdba-Ausdruck nochmals kürzer als die Schreibweise mit der anonymen Methode. Ein Lambda-Ausdruck besteht im Wesentlichen aus drei Teilen:

  • eine Parameterliste, die in () eingeschlossen ist (wenn keine Parameter übergeben werden, so müssen leere Klammern angegeben werden). Wird nur ein Paramter benötigt, können die Klammern auch weggelassen werden.
  • => – Operator
  • einem Funktionsblock eingeschlossen in {}. Wird dagegen nur eine Anweisung angegeben, können die geschweiften Klammern weggelassen werden

Hier mal einige Beispiele für gültige Lamda-Ausdrücke:

  • () => DoSomething();
  • x => x * x
  • (int x) => x + x

Rückgabetyp

Der Rückgabetyp eines Lambda-Ausdrucks wird durch den Typ des Ausdruckes hinter dem =>-Operator bestimmt. Folgt nach dem =>-Operator lediglich eine Anweisung hat der Lambda-Ausdruck den gleichen Rückgabetyp wie diese Anweisung (werden z.B. zwei Integer-Werte addiert ist der Rückgabetyp folglich auch vom Typ Integer). Falls ein Anweisungsblock (mehrere Statements) folgt, wird der Rückgabetyp durch den Ausdruck nach dem return festgelegt. Fehlt return im Anweisungsblock ist der Rückgabetyp des Lambda-Ausdrucks void. Hier mal ein paar Beispiele:

Ein Lambda-Ausdruck kann auch für die Ereignisbehandlung eingesetzt werden (ähnlich dem Beispiel mit der anonymen Methode):

Func- und Action-Delegaten

Func– und Action-Delegaten sind vordefinierte, generische Delegaten, um eine Methode als Parameter zu übergeben, ohne dabei explizit eine eigenen benutzerdefinierten Delegaten definieren zu müssen. Hierfür muss die gekapselte Methode nur der Methodensignatur entsprechen, die von diesen Delegaten vorgegeben wird. Im Gegensatz zu den Action-Delegaten (geben keinen Wert zurück) haben die Func-Delegaten eine generischen Rückgabewert. Diese Typen sind so flexibel und generisch, dass sich damit praktisch alle Fälle abbilden lassen, ohne eigene Delegaten schreiben zu müssen. Damit lässt sich das oben gezeigte Beispiel wie folgt umschreiben:

So damit wären wir auch schon fertig mit den Grundlagen zu Delegaten, anonymen Methoden und Lambda-Ausdrücken. Bei Gelegenheit werde ich noch einen weiteren Artikel zu diesem Thema schreiben, der ein wenig mehr ins Detail geht (z.B. praktisches Anwendungsbeispiel, Ausdrucks- und Anweisungslambdas, Varianz, usw.).

Hier zunächst einmal der Verweis auf weitere Onlinequellen zu diesem Thema:

Literaturverzeichnis und Weblinks

Abk.Quelle
[1]Delegate
[2]Anonyme Methoden
[3]Lambda-Ausdrücke
[4]Action-Delegate
[5]Func-Delegat

leave your comment

Fork me on GitHub