Kolumne: C# im Fokus

Lambda Expressions und Expression Trees
Kommentare

Mithilfe von Expression Trees kann ausführbarer Code in Datenstrukturen überführt werden. Dies ermöglicht die Modifizierung des Codes vor der eigentlichen Ausführung. Expression Trees enthalten somit Code in einer hierarchischen Struktur, wobei die einzelnen Knoten jeweils eine Expression repräsentieren. Eine Expression stellt dabei eine Methode oder eine binäre Operation dar.

Expression Trees und Lambda Expressions sind eng miteinander verwandt. Jedoch unterscheiden sie sich immens in ihrer Funktion. Bei Lambda Expression handelt es sich um direkt ausführbaren Code, wobei ein Expression Tree eine Lambda Expression als Datenstruktur darstellt. Im Folgenden werden die speziellen Eigenschaften und Verwendungsmöglichkeiten von Lambda-Ausdrücken und Expression Trees erläutert.

Lambda Expressions

Mit der C#-Version 3.0 wurden Lambda Expressions als neues Sprachmerkmal eingeführt. Lambda Expression erlauben die verkürzte Definition einer anonymen Methode. Ebenfalls können mithilfe von Lambda Expressions so genannte Expression Trees angelegt werden. Die Verwendung und Funktionsweise von anonymen Methoden wurde schon in der Ausgabe 08.2010 des .NET Magazins erläutert. Um die Syntax der Lambda Expression besser verstehen zu können, wird im Folgenden erklärt, wie ein „normaler“ Delegate zu einem Lambda-Ausdruck umgewandelt werden kann. Listing 1 zeigt dazu ein entsprechendes Beispiel.

private static void DelegteLambdas()
{
  // Verwendung eines benannten Funktionszeigers
  List arr = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  List result = arr.FindAll(new Predicate(FindDelegate));
  // Ausnutzung von "Type Inference". Die explizite Angabe des 
  // Typen kann entfallen
  result = arr.FindAll(FindDelegate);
  // Verwendung einer anonymen Methode
  result = arr.FindAll(delegate(int input) { return input % 2 == 0; });
  // Verwendung einer Lambda Expression
  result = arr.FindAll(input => input % 2 == 0);
}
private static bool FindDelegate(int input) {
  return input % 2 == 0;
}  

Mithilfe der FindAll-Methode der generischen List-Klasse sollen alle geraden Zahlen innerhalb der Liste gefunden werden. Der FindAll-Methode muss dazu ein Delegate vom Typ Predicate übergeben werden. Der Predicate Delegate ist im Namensraum System wie folgt definiert: public delegate bool Predicate(T obj).

Bei dem Übergabeparameter handelt es sich um einen generischen Typ, der sich jeweils an den in der Liste befindlichen Typ anpasst. Als Rückgabewert signalisiert ein bool-Typ, ob das aktuelle Listenelement den definierten (Such-/Filter-)Kriterien entspricht. Bei der ersten Umsetzung des Filters in Listing 1 wurde die benannte FindDelegate-Methode als Ziel für den Predicate Delegate verwendet. Der zweite Ansatz verwendet ebenfalls die definierte Methode FindDelegate, hier wird lediglich auf die explizite Angabe des Delegate-Typen verzichtet. Der richtige Typ wird in diesem Fall mittels Type Inference automatisch durch den Compiler während der Übersetzungszeit ermittelt. Die dritte Variante verzichtet auf eine benannte Methode und definiert mittels anonymer Methode die notwendige Logik direkt bei der Übergabe an die FindAll-Methode. Die letzte Alternative wandelt nun die anonyme Methode zu einer Lambda Expression wie folgt um: input => input % 2 == 0. Auf der linken Seite einer Lambda Expression werden jeweils die Eingabeparameter definiert, dann folgt der Zuweisungsoperator => (gesprochen: „geht nach“). Auf der rechten Seite wird der eigentliche Methodenblock – also die eigentliche Logik – implementiert. Besteht die Anweisung lediglich aus einer Anweisung wie in dem Beispiel, muss kein neuer Block (mittels {}) und keine return-Anweisung angegeben werden. Wie in dem Beispiel deutlich wird, kann die übergebene Lambda Expression direkt durch die FindAll-Methode ausgeführt werden. Dies gilt für jede Lambda Expression. Listing 2 verdeutlicht die direkte Ausführbarkeit einer Lambda Expression anhand eines komplexen, so genannten Lambda Statements.

private static void ComplexLambda()
{
  Func Method = (a,b,c) => {
    int z = a;
    if (b > z)
      z = b;
    if (c > z)
      z = c;
    return z;
  };
  int result = Method(17, 35, 9);
}  

Zunächst wird eine neue Methode definiert, deren Referenz in der Variablen Method vom Typ Delegate gespeichert wird. Auf die Definition einer entsprechenden Delegate-Signatur kann verzichtet werden, da im Namensraum System bereits entsprechende Überladungen des generischen Func Delegates vorhanden sind. Die Aufgabe der Methode besteht darin, aus drei übergebenen Zahlen die größte zu ermitteln und zurückzugeben. Die Übergabeparameter werden – wie bereits beschrieben – auf der linken Seite der Expression notiert, in diesem Fall handelt es sich um die Angabe: (a, b, c). Auf der rechten Seite wird die eigentliche Logik definiert. Da die Methode mehrere Anweisungen besitzt, muss ein Kontrollblock geöffnet und am Ende eine return-Anweisung erfolgen. Abbildung 1 verdeutlicht die Syntax einer Lambda Expression im Vergleich zu einer üblichen Methode. Im Anschluss kann die Methode, wie in Listing 2 gezeigt, direkt aufgerufen werden.

Abb. 1: Zusammenhang zwischen benannter Methode und Lambda Expression
Abb. 1: Zusammenhang zwischen benannter Methode und Lambda Expression
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -