Advanced  Services Anonyme Delegates und Lambdas

Was ist ein anonymes Delegate

Ein anonymes Delegate ist ein namenloses Delegateobjekt und kann überall dort verwendet werden, wo man ein Delegate-Objekt einsetzen kann.


Was ist ein Lambdaausdruck

Ein Lambdaausdruck ist eine abkürzende Schreibweise für ein anonymes Delegate und kann überall dort eingesetzt werden, wo man ein anonymes Delegate verwenden kann.


Einige Beispiele

Die folgenden Beispiele verlaufen immer nach dem gleichen Schema. Ausgehend von einem Delegate dem man einen Methodennamen übergeben kann wird anschließend der Methodenname durch eine anonymes Delegate ersetzt und dieses schließlich durch einen Lambdaausdruck.


Beispiel 1

Das erste Beispiel geht von der folgenden Delegatedeklaration aus:

public delegate double DoubleFunction(double d);

Eine dazu passende Methode gibt es beispielsweise in der Klasse Math

// Delegatevariable erhält Methodennamen aus der Klasse Math
DoubleFunction mydel = new DoubleFunction(Math.Exp);
Console.WriteLine(mydel(2));  // e^2

Als nächstes nehmen wir die folgende Methode (falls die Methode in Main verwendet wird muß sie statisch sein).

public static double HochDrei(double d)
{
   return d * d * d;
}

Nun ersetzen wir schrittweise ein Delegateobjekt durch ein anonymes Delegate und schließlich durch einen Lambda-Ausdruck. Dabei verwenden wir immer den Funktionscode, der in der Methode HochDrei auftaucht.

Es wird der name der selbstcodierten Methode an die Delegatevariable übergeben

DoubleFunction mydel = HochDrei;
Console.WriteLine(mydel(2));

Anonymes Delegate mit derselben Funktion

DoubleFunction mydel = delegate(double x) { return x * x * x; };
Console.WriteLine(mydel(2));  //

Lambda-Ausdruck mit expliziter Typisierung

DoubleFunction mydel = ( (double x) => x * x * x );
Console.WriteLine(mydel(2));  //

Lambda-Ausdruck mit impliziter Typisierung

DoubleFunction mydel = (x => x * x * x);
Console.WriteLine(mydel(2));  //

Es geht auch noch kürzer, allerdings ist hier ein Cast notwendig, da der Compiler für den Lambda-Ausdruck (z => z * z * z) einen Typ benötigt.

Console.WriteLine( ((DoubleFunction)(z => z * z * z))(2) )  ;


Beispiel 2

In diesem Beispiel verwenden wir zwei in der API vordefinerte Delegates, Predicate und Action, die beide im Namespace System liegen.

Beide Delegates haben offensichtlich den generischen Typ T.

Im Codeteil eines Predicate kann man etwa untersuchen, ob das übergebene Objekt ein gewisses Kriterium erfüllt und dementsprechend true oder false zurückgeben. Hat man eine Sammlung von Objekten, so kann man mit Hilfe dieser Methode herausfinden welcher Teil der Sammlung ein gewünschtes Kriterium erfüllt. Eine Anwendung hierzu findet man in der Klasse List. Die Klasse List liegt in System.Collections.generic und ist eine generische Behälterklasse deren Objekte eine beliebige Anzahl anderer Objekte aufnehmen kann. Diese Klasse besitzt gleich eine ganze Reihe von Methoden die ein Predicate verwenden.

Weitere Methoden, die ein Predicate verwenden sind FindIndex, FindLast, FindLastIndex, RemoveAll und TrueForAll.

Wir wollen die Methode FindAll verwenden. Dazu legen eine generische Liste von Strings an und füllen diese mit den Werten "eins", "zweins", "dreins", "vierns", "fuenfs". Wir suchen nun alle Listenelemente, der Anfangsbuchstabe größer als 'm' ist, die also in der zweiten Hälfte des Alphabets liegen (als Ergebnis sollte sich "zweins" und "vierns" einstellen). Zunächst schreiben wir wieder eine Methode, die untersucht ob dieses Kriterium erfüllt ist. Anschließend verwenden dann einen Lambda-Ausdruck.

Hier zunächst die Methode. Wir nennen sie MatchCriteria.

private static bool MatchCriteria(String s)
{
   if (s[0] > 'm') return true;
   return false;
}

Jetzt der Hauptteil:

List<(String)> = new List<String>();
list.Add("eins");
list.Add("zweins");
list.Add("dreins");
list.Add("vierns");
list.Add("fuenfs");

Ausführliche Fassung mit einem Objekt vom Typ Predicate und einer eigener Methode

Predicate<String> predicate = new Predicate<String>(MatchCriteria);
List<(String)> trefferListe = list.FindAll(predicate);
foreach (string st in trefferListe)
   Console.WriteLine(st);

Leicht gekürzte Fassung ohne Predicate Objekt, Methode wird direkt an FindAll übergeben

trefferListe = list.FindAll(matchCriteria);
foreach (string st in treffer)
   Console.WriteLine(st);

Fassung mit einem anonymen Delegate

trefferListe = list.FindAll( delegate(String st) {if (st[0] > 'k') return true; return false;} );
foreach (string st in treffer)
   Console.WriteLine(st);

Fassung mit Lambda-Ausdruck mit expliziter Typisierung

trefferListe = list.FindAll( (String st) => { return (st[0] > 'k') ? true : false; });
foreach (string st in treffer)
   Console.WriteLine(st);

Fassung mit Lambda-Ausdruck ohne explizite Typisierung

trefferListe = list.FindAll( st => { return (st[0] > 'k') ? true : false; });
foreach (string st in treffer)
   Console.WriteLine(st);

Fassung mit Lambda-Ausdruck ohne explizite Typisierung und ohne return.

trefferListe = list.FindAll( st => st[0] > 'k' ? true : false );
foreach (string st in treffer)
   Console.WriteLine(st);

Wenn wir nun das Delegate Action einsetzen, können wir noch knapper formulieren. Jede Liste verfügt nämlich über eine ForEach() Methode die ein Delegate Action erwartet:

trefferListe = list.FindAll( st => st[0] > 'k' ? true : false );
trefferListe.ForEach( st => Console.WriteLine(st) );

Und es geht auch in einer Zeile...

list.FindAll(st => st[0] > 'k' ? true : false).ForEach(st => Console.WriteLine(st));

Zwei Beispiele mit Threads

Als weiteres Beispiel bieten sich Threads an. Threads funktionieren bekanntlich über Delegates. Es gibt für Threads die folgenden zwei Delegates:

Das erste Delegate ist zuständig für Funktionen die keinen Parameter haben und keinen Wert zurückgeben, das zweite Delegate ist zuständig für Funktionen die einen Parameter vom Typ Object erhalten und keinen Wert zurückgeben:


Beispiel 1

Da unsere Themen Delegates und Lambdas sind verwenden wir für das parameterlose Delegate die folgende simple Methode, die wir über einen Thread starten wollen. Wir werden dann wieder schrittweise über ein anonymes Delegate zu einem Lambda-Ausdruck kommen.

// methode für das parameterlose delegate
public static void Method()
{
   Console.WriteLine("hello");
}

Die folgende Main-Methode legt zuerst ein Delegate an, das die obige Methode verwendet. In mehreren Schritten entsteht dann der Lambda-Ausdruck..

static void Main(string[] args)
{
   // vom delegate zum anonymen delegate zum lambda-ausdruck
   ThreadStart startDelegate = new ThreadStart(method);
   Thread t1 = new Thread(startDelegate);
   t1.Start();

   // oder kürzer
   Thread t2 = new Thread(method);
   t2.Start();

   // anonymes delegate
   Thread t3 = new Thread( delegate() { Console.WriteLine("hello"); } );
   t3.Start();

   // lambda
   // regel: ersetze das schlüsselwort delegate durch den pfeil =>
   // der nach der klammerpaar () gesetzt wird
   Thread t4 = new Thread( () => { Console.WriteLine("hello"); } );
   t4.Start();

   // lambda, noch knappere form möglich,
   // da nur ein statement in den geschweiften klammern steht
   Thread t5 = new Thread( () => Console.WriteLine("hello") );
   t5.Start();
}

Beispiel 2

Für das parametrisierte Delegate verwenden wir die folgende ebenso simple Methode, die wir auch wieder über einen Thread starten wollen. Dann geht es wieder schrittweise über ein anonymes Delegate zu einem Lambda-Ausdruck.

// methode für das delegate mit dem parameter vom typ object
public static void method2(object ob)
{
   Console.WriteLine(ob);
}
static void Main(string[] args)
{
   // jetzt mit dem delegate mit parameter vom typ object
   ParameterizedThreadStart paramDelegate = new ParameterizedThreadStart(method2);
   Thread t6 = new Thread(paramDelegate);
   t6.Start("hello 2");

   // kürzer mit direkter übergabe der methode an den thread konstruktor
   Thread t7 = new Thread(method2);
   t7.Start("hello 2");

   // mit anonymen delegate
   Thread t8 = new Thread( delegate (object ob) {Console.WriteLine(ob);} );
   t8.Start("hello 2");

   // mit lambda ausdruck und expliziter typisierung
   // regel: ersetzte schlüsselwort delegate durch den pfeil =>
   // nach dem klammerpaar (object ob)
   Thread t9 = new Thread( (object ob) => { Console.WriteLine(ob); });
   t9.Start("hello 2");

   // mit lambda ausdruck und ohne typisierung
   Thread t10 = new Thread( (ob) => { Console.WriteLine(ob); });
   t10.Start("hello 2");

   // mit lambda ausdruck, ohne typisierung und ohne geschweifte klammern
   // da nur ein statement in den geschweiften klammern steht
   Thread t11 = new Thread((ob) => Console.WriteLine(ob) );
   t11.Start("hello 2");
}

Schema zum Erstellen eines Lambda-Ausdrucks

Aus den vorhergehenden Beispielen wollen wir ein Schema ableiten wie man von einem Delegate bzw. einer Methode zu einem anonymen Delegate und schließlich zu einem Lambda-Ausdruck gelangt.

Eine Delegatedeklaration beginnt immer mit dem Schlüsselwort delegate und hat den folgenden allgemeinen Aufbau:

Die korrespondierende Methode hat den folgenden allgemeinen Aufbau:

Hieraus Erstellen wir ein anonymes Delegate indem wir mit dem Schlüsselwort delegate beginnen, dahinter die Parameterliste setzen und darauffolgend den Codeteil. Der Returntyp ergibt sich implizit und wird nicht explizit aufgeführt. Da es zu einem anonymen Delegate immer eine Delegatedeklaration geben muß, kann der Compiler über diese Deklaration den Returntyp ermitteln. Da sich der Returntyp auch aus der Returnanweisung im Codeteil ermitteln läßt, hat der Compiler zudem die Möglichkeit festzustellen ob der im Codeteil verwendete Returntyp mit dem der Deklaration übereinstimmt.

Anonymes Delegate

Der Lambda-Ausdruck ersetzt nun einfach das Schlüsselwort delegate durch den Pfeil =>, setzt diesen aber zwischen Parameterliste und dem in geschweiften Klammern stehenden Codeteil.

Lambda-Ausdruck (vollständig)

Da der Compiler die Typen in der Parameterliste auch der Deklaration entnehmen kann, kann man sogar eine Parameterliste ohne Datentypen erstellen:

Lambda-Ausdruck (ohne Datentypangabe)

Besteht der Codeteil nur aus einer einzigen Anweisung kann man sogar die geschweiften Klammern (einschließlich Semikolon !) weglassen.

Lambda-Ausdruck (ohne Datentypangabe, Codeteil besteht nur aus einer Anweisung)