Ausdrucksbaumstrukturen

Alle Ausdrucksbaumstrukturen, die Sie bisher gesehen haben, wurden vom C#-Compiler erstellt. Sie haben einen Lambdaausdruck erstellt, der einer als Expression<Func<T>> oder ähnlicher Typ typisierten Variablen zugewiesen wurde. In vielen Szenarios erstellen Sie zur Laufzeit einen Ausdruck im Arbeitsspeicher.

Ausdrucksbaumstrukturen sind unveränderlich. Unveränderlich bedeutet, dass Sie die Struktur von den Blättern bis zum Stamm erstellen müssen. Die APIs, die Sie zum Erstellen von Ausdrucksbaumstrukturen verwenden, spiegeln diese Tatsache wider: Die Methoden, die Sie verwenden, um einen Knoten zu erstellen, verwenden alle ihre untergeordneten Elemente als Argumente. Betrachten wir einige Beispiele, die Ihnen die Techniken zeigen.

Erstellen von Knoten

Sie beginnen mit dem Additionsausdruck, mit dem Sie in diesen Abschnitten gearbeitet haben:

Expression<Func<int>> sum = () => 1 + 2;

Um diese Ausdrucksbaumstruktur zu erstellen, erstellen Sie zuerst die Blattknoten. Die Blattknoten sind Konstanten. Verwenden Sie die Constant-Methode, um die Knoten zu erstellen:

var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));

Als Nächstes erstellen Sie den Additionsausdruck:

var addition = Expression.Add(one, two);

Sobald Sie den Additionsausdruck erstellt haben, erstellen Sie den Lambdaausdruck:

var lambda = Expression.Lambda(addition);

Dieser Lambdaausdruck enthält keine Argumente. In diesem Abschnitt erfahren Sie, wie Sie Parametern Argumente zuordnen und kompliziertere Ausdrücke erstellen.

Für Ausdrücke wie diesen können Sie alle Aufrufe in einer einzelnen Anweisung kombinieren:

var lambda2 = Expression.Lambda(
    Expression.Add(
        Expression.Constant(1, typeof(int)),
        Expression.Constant(2, typeof(int))
    )
);

Erstellen einer Struktur

Im vorherigen Abschnitt wurden die Grundlagen zum Erstellen einer Ausdrucksstruktur im Arbeitsspeicher erläutert. Komplexere Strukturen bedeuten im Allgemeinen mehr Knotentypen und weitere Knoten in der Struktur. Lassen Sie uns ein weiteres Beispiel ausführen, und zwei weitere Knotentypen zeigen, die Sie in der Regel beim Erstellen von Ausdrucksbaumstrukturen erstellen: Die Argumentknoten und Methodenaufrufknoten. Wir erstellen eine Ausdrucksbaumstruktur, um diesen Ausdruck zu erstellen:

Expression<Func<double, double, double>> distanceCalc =
    (x, y) => Math.Sqrt(x * x + y * y);

Sie beginnen mit dem Erstellen der Parameterausdrücke für x und y:

var xParameter = Expression.Parameter(typeof(double), "x");
var yParameter = Expression.Parameter(typeof(double), "y");

Das Erstellen der Multiplikations- und Additionsausdrücke folgt dem Muster, das Sie bereits gesehen haben:

var xSquared = Expression.Multiply(xParameter, xParameter);
var ySquared = Expression.Multiply(yParameter, yParameter);
var sum = Expression.Add(xSquared, ySquared);

Anschließend müssen Sie einen Ausdruck des Methodenaufrufs für den Aufruf von Math.Sqrt erstellen.

var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) }) ?? throw new InvalidOperationException("Math.Sqrt not found!");
var distance = Expression.Call(sqrtMethod, sum);

Der GetMethod-Aufruf könnte null zurückgeben, wenn die Methode nicht gefunden wird. Höchstwahrscheinlich liegt das daran, dass Sie den Methodennamen falsch geschrieben haben. Andernfalls könnte dies bedeuten, dass die erforderliche Assembly nicht geladen wird. Schließlich platzieren Sie den Methodenaufruf in einen Lambdaausdruck und stellen sicher, dass Sie die Argumente für den Lambdaausdruck definieren:

var distanceLambda = Expression.Lambda(
    distance,
    xParameter,
    yParameter);

In diesem komplizierteren Beispiel sehen Sie ein paar weitere Verfahren, die Sie häufig benötigen, um Ausdrucksbaumstrukturen zu erstellen.

Zunächst müssen Sie die Objekte erstellen, die Parameter oder lokale Variablen darstellen, bevor Sie sie verwenden. Wenn Sie diese Objekte erstellt haben, können Sie diese in Ihrer Ausdrucksbaumstruktur verwenden, wo immer Sie sie benötigen.

Zweitens müssen Sie einen Teil der Reflektions-APIs verwenden, um ein System.Reflection.MethodInfo-Objekt zu erstellen, sodass Sie eine Ausdrucksbaumstruktur für den Zugriff auf diese Methode erstellen können. Sie müssen sich auf die Teilmenge der Reflektions-APIs begrenzen, die auf der .NET Core-Plattform verfügbar sind. In diesem Fall werden diese Techniken auf andere Ausdrucksbaumstrukturen erweitert.

Erstellen von Code im Detail

Sie sind nicht darin beschränkt, was Sie mithilfe dieser APIs erstellen können. Je komplizierter jedoch die Ausdrucksbaumstruktur ist, die Sie erstellen möchten, desto schwieriger ist der Code zu verwalten und zu lesen.

Wir erstellen eine Ausdrucksbaumstruktur, die diesem Code entspricht:

Func<int, int> factorialFunc = (n) =>
{
    var res = 1;
    while (n > 1)
    {
        res = res * n;
        n--;
    }
    return res;
};

Der vorangehende Code hat nicht die Ausdrucksstruktur erstellt, sondern einfach den Delegaten. Mithilfe der Expression-Klasse können Sie keine Anweisungslambdas erstellen. Hier ist der Code, der erforderlich ist, um die gleiche Funktionalität zu erstellen. Es gibt keine API zum Erstellen einer while-Schleife. Stattdessen müssen Sie eine Schleife, die einen bedingten Test enthält, und ein Bezeichnungsziel erstellen, um die Schleife zu unterbrechen.

var nArgument = Expression.Parameter(typeof(int), "n");
var result = Expression.Variable(typeof(int), "result");

// Creating a label that represents the return value
LabelTarget label = Expression.Label(typeof(int));

var initializeResult = Expression.Assign(result, Expression.Constant(1));

// This is the inner block that performs the multiplication,
// and decrements the value of 'n'
var block = Expression.Block(
    Expression.Assign(result,
        Expression.Multiply(result, nArgument)),
    Expression.PostDecrementAssign(nArgument)
);

// Creating a method body.
BlockExpression body = Expression.Block(
    new[] { result },
    initializeResult,
    Expression.Loop(
        Expression.IfThenElse(
            Expression.GreaterThan(nArgument, Expression.Constant(1)),
            block,
            Expression.Break(label, result)
        ),
        label
    )
);

Der Code zum Erstellen der Baumstruktur für die Fakultätsfunktion ist etwas länger, komplizierter, und er ist voll von Bezeichnungen und Break-Anweisungen und anderen Elemente, die Sie in Ihren täglichen Codieraufgaben vermeiden möchten.

Für diesen Abschnitt haben Sie Code geschrieben, um jeden Knoten in dieser Ausdrucksstruktur aufzusuchen und Informationen zu den Knoten auszugeben, die in diesem Beispiel erstellt werden. Sie können den Beispielcode vom Repository „dotnet/docs“ auf GitHub anzeigen oder herunterladen. Experimentieren Sie selbst, indem Sie die Beispiele erstellen und ausführen.

Zuordnen von Codekonstrukten zu Ausdrücken

Das folgende Codebeispiel veranschaulicht eine Ausdrucksbaumstruktur, die den Lambdaausdruck num => num < 5 mithilfe der API verkörpert.

// Manually build the expression tree for
// the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
    Expression.Lambda<Func<int, bool>>(
        numLessThanFive,
        new ParameterExpression[] { numParam });

Die Ausdrucksbaumstruktur-API unterstützt auch Zuweisungen und Ablaufsteuerungsausdrücke wie Schleifen, bedingte Blöcke und try-catch-Blöcke. Mithilfe der API können Sie Ausdrucksbaumstrukturen erstellen, die komplexer sind als diejenigen, die von Lambda-Ausdrücken vom C#-Compiler erstellt werden. Im folgenden Beispiel wird veranschaulicht, wie eine Ausdrucksbaumstruktur erstellt wird, welche die Fakultät einer Zahl berechnet.

// Creating a parameter expression.
ParameterExpression value = Expression.Parameter(typeof(int), "value");

// Creating an expression to hold a local variable.
ParameterExpression result = Expression.Parameter(typeof(int), "result");

// Creating a label to jump to from a loop.
LabelTarget label = Expression.Label(typeof(int));

// Creating a method body.
BlockExpression block = Expression.Block(
    // Adding a local variable.
    new[] { result },
    // Assigning a constant to a local variable: result = 1
    Expression.Assign(result, Expression.Constant(1)),
        // Adding a loop.
        Expression.Loop(
           // Adding a conditional block into the loop.
           Expression.IfThenElse(
               // Condition: value > 1
               Expression.GreaterThan(value, Expression.Constant(1)),
               // If true: result *= value --
               Expression.MultiplyAssign(result,
                   Expression.PostDecrementAssign(value)),
               // If false, exit the loop and go to the label.
               Expression.Break(label, result)
           ),
       // Label to jump to.
       label
    )
);

// Compile and execute an expression tree.
int factorial = Expression.Lambda<Func<int, int>>(block, value).Compile()(5);

Console.WriteLine(factorial);
// Prints 120.

Weitere Informationen finden Sie unter Generating Dynamic Methods with Expression Trees in Visual Studio 2010 (Generieren dynamischer Methoden mit Ausdrucksbaumstrukturen in Visual Studio 2010). Dieser Artikel gilt auch für höhere Versionen von Visual Studio.