Lambda Expressions und Expression Trees (Teil 2)
Kommentare

Expression Trees
Im vorherigen Abschnitt wurden die Syntax sowie die Verwendung von Lambda Expression ausführlich demonstriert. Wie stehen diese nun zu Expression Trees?
Syntaktisch betrachtet existieren

Expression Trees

Im vorherigen Abschnitt wurden die Syntax sowie die Verwendung von Lambda Expression ausführlich demonstriert. Wie stehen diese nun zu Expression Trees?

Syntaktisch betrachtet existieren keinerlei Differenzen zwischen Lambda Expressions und Expression Trees. Um dies zu verdeutlichen, zeigt Listing 3 die in Listing 1 verwendete Lambda Expression als Expression Tree.

private static void ExpressionTree1()
{
  Expression> expression = input => input % 2 == 0;
}  

Bei der Zuweisung des Lambda-Ausdrucks an die Variable expression vom Typ Expression erstellt der Compiler automatisch einen entsprechenden Expression Tree. Dies ist allerdings nur möglich, wenn es sich bei dem Lambda-Ausdruck um eine Expression handelt. Lambda-Definitionen, die einen Anweisungsblock besitzen – so genannte Statement Lambdas – werden nicht unterstützt. Der resultierende Expression Tree stellt den Lambda-Ausdruck als Datenstruktur dar. Das heißt, die einzelnen Bestandteile des Ausdrucks können mithilfe der Instanz vom Typ Expression ausgewertet werden. Listing 4 zeigt einige Analysemöglichkeiten und Abbildung 2 die resultierenden Konsolenausgaben.

Abb. 2: Auswertung eines Expression Trees
Abb. 2: Auswertung eines Expression Trees
private static void ExpressionTree1()
{
  Expression> expression = input => input % 2 == 0;

  Console.WriteLine("Expression Tree: input => input % 2 == 0");
  Console.WriteLine("Body: {0}", expression.Body );
  Console.WriteLine("Node type: {0}", expression .NodeType );
  Console.WriteLine("Type: {0}", expression.Type.ToString());
  Console.WriteLine("Parameters: {0}", expression.Parameters.Count );      
  foreach(ParameterExpression param in expression.Parameters)
    Console.WriteLine(" Parrameter: '{0}' Typ: {1}", param.Name, param.Type);
}  

Über die Eigenschaft NodeType kann der Typ der Expression ermittelt werden. In diesem Fall handelt es sich, wie es die Konsolenausgabe zeigt, um eine Lambda Expression. Alle möglichen Expression-Typen sind in der Auflistung ExpressionType zusammengefasst. Die Body-Eigenschaft enthält die zugehörige Lambda Expression. Die definierten Eingabeparameter können über die Eigenschaft Parameters ermittelt werden. Der Typ des Rückgabewerts kann über die Type-Eigenschaft ausgelesen werden. Im Gegensatz zu einer Lambda Expression ist ein Expression Tree aber nicht direkt ausführbar. Um ein Expression Tree in eine ausführbare Version überführen zu können, steht die Methode Compile bereit. Listing 5 demonstriert, wie die Expression Tree kompiliert und in ausführbaren Code umgewandelt werden kann.

private static void ExpressionTree2()
{
  List arr = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  Expression> expression = input => input % 2 == 0;
  Func executeable = expression.Compile();
  List result = arr.FindAll(new Predicate(executeable));
}  

Dazu wird die Methode Compile eingesetzt, die als Rückgabewert einen Delegate liefert. Der erzeugte Delegate kann dann wieder als Filterfunktion für die FindAll-Methode, wie es Listing 5 zeigt, verwendet werden. Die hier gezeigte Implementierung dient allerdings nur als Beispiel. Im Normalfall werden für solche Vorgänge keine Expression Trees eingesetzt. Expression Trees kommen eher zum Einsatz, wenn Code vor der Ausführung modifiziert oder in anderen Prozessräumen ausgeführt werden muss. Ein Expression Tree muss daher auch nicht zwangsläufig in IL-Code enden. Da ein Expression Tree lediglich beschreibt, was und nicht wie etwas ausgeführt werden soll, kann die Expression prozessübergreifend weitergegeben und innerhalb fremder Prozesse ausgeführt werden. Ein bekanntes Beispiel dafür ist LINQ to SQL. Innerhalb des eigenen C#- bzw. Visual-Basic-Programms wird LINQ verwendet, um Datenabfragen zu definieren. Die definierte LINQ-Abfrage wird – im Fall von LINQ to SQL – durch den entsprechenden (LINQ-to-SQL-)Provider in T-SQL Statements umgewandelt und innerhalb des MS-SQL-Server-Prozesses ausgeführt. Der zugehörige Expression Tree beschreibt, welches Ergebnis erwartet wird und der (LINQ-to-SQL-)Provider entscheidet, wie das Ergebnis konkret erzielt werden kann. Hierbei hat der Provider alle Freiheiten und könnte sogar während der Erstellung der Abfrage Rahmenparameter wie die aktuelle Last auf den SQL Server berücksichtigen.

Expression Trees manuell erstellen

Im vorherigen Abschnitt wurde aus einer bestehenden Lambda Expression ein Expression Tree erzeugt. Dazu musste lediglich die Lambda Expression dem Datentypen Expression zugewiesen werden. Expression Trees können aber auch manuell erstellt werden und eröffnen so die Möglichkeit, einem Programm zur Laufzeit neuen Code hinzuzufügen. Bevor Expression Trees verfügbar waren, musste manuell MSIL-Code generiert und dem Programm hinzugefügt werden. Listing 6 verdeutlicht die dynamische Codegenerierung.

private static void DynamicExpression()
{
  ParameterExpression i = Expression.Parameter(typeof(int), "i");
  ParameterExpression loops = Expression.Parameter(typeof(int), "loops");
  ParameterExpression output = Expression.Parameter(typeof(string), "output");
  LabelTarget exitLoop = Expression.Label(typeof(void));

  BlockExpression block = Expression.Block(
    Expression.Loop(
      Expression.Block(
        Expression.Call(null,typeof(Console).
GetMethod("WriteLine", new Type[] { typeof(String) }), output),
        Expression.PostIncrementAssign(i),
        Expression.IfThen(Expression.GreaterThan(i, loops), 
Expression.Break(exitLoop))
        ), exitLoop
    )        
  );
  Expression.Lambda>(block, new ParameterExpression[] 
  { i, loops, output }).Compile()(0, 5, "Dynamisch erstellte Methode");
}  

Die dynamische Methode setzt eine Schleife um, die einen Text auf der Konsole ausgibt. Wie oft und welcher Text ausgegeben wird, kann über Parameter gesteuert werden. Wie erkennbar ist, können über die vorhandenen Expression-Factory-Methoden auch komplexe Algorithmen umgesetzt werden. Dies vereinfacht die Erstellung dynamischer Methoden zur Laufzeit erheblich.

Marc André Zhou arbeitet als Senior Consultant bei der Logica Deutschland GmbH & Co. KG. Seine Schwerpunkte liegen im Bereich Softwarearchitekturen und Frameworks, hier hauptsächlich im .NET-Umfeld. Sie erreichen ihn per E-Mail.
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -