Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Der C#-Compiler hat alle Ausdrucksbäume erstellt, die Sie bisher gesehen haben. Sie haben einen Lambda-Ausdruck erstellt, der einer Variablen zugewiesen ist, die als Expression<Func<T>>
oder ein ähnlicher Typ eingegeben wurde. In vielen Szenarios erstellen Sie zur Laufzeit einen Ausdruck im Arbeitsspeicher.
Ausdrucksbäume sind unveränderlich. Unveränderlich zu sein bedeutet, dass Sie den Baum von den Blättern bis zur Wurzel 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. Sehen wir uns ein paar Beispiele an, um Ihnen die Techniken zu zeigen.
Erstellen von Knoten
Sie beginnen mit dem Zusatzausdruck, mit dem Sie in den folgenden Abschnitten gearbeitet haben:
Expression<Func<int>> sum = () => 1 + 2;
Um diesen Ausdrucksbaum zu konstruieren, 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);
Nachdem Sie den Additionsausdruck erstellt haben, erstellen Sie den Lambda-Ausdruck:
var lambda = Expression.Lambda(addition);
Dieser Lambda-Ausdruck enthält keine Argumente. Später in diesem Abschnitt erfahren Sie, wie Sie Argumenten Parametern zuordnen und komplexere 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))
)
);
Baue einen Baum auf
Im vorherigen Abschnitt wurden die Grundlagen zum Erstellen einer Ausdrucksstruktur im Arbeitsspeicher erläutert. Komplexere Strukturen bedeuten im Allgemeinen mehr Knotentypen und mehr Knoten in der Struktur. Führen wir ein weiteres Beispiel durch und zeigen zwei weitere Knotentypen an, die Sie normalerweise erstellen, wenn Sie Ausdrucksstrukturen 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);
Zunächst erstellen Sie 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. Das liegt wahrscheinlich daran, dass Sie den Methodennamen falsch geschrieben haben. Andernfalls könnte dies bedeuten, dass die erforderliche Assembly nicht geladen wird. Schließlich fügen Sie den Methodenaufruf in einen Lambda-Ausdruck ein, und stellen Sie sicher, dass Sie die Argumente für den Lambda-Ausdruck 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. Nachdem Sie diese Objekte erstellt haben, können Sie sie überall in Ihrem Ausdrucksbaum verwenden.
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 selbst auf die Teilmenge der Reflection-APIs beschränken, die auf der .NET Core-Plattform verfügbar sind. Auch hier erstrecken sich diese Techniken auf andere Ausdrucksbäume.
Erstellen von Code im Detail
Sie sind nicht darauf beschränkt, was Sie mit diesen APIs erstellen können. Je komplizierter die Ausdrucksstruktur ist, die Sie erstellen möchten, desto schwieriger ist es, den Code zu verwalten und zu lesen.
Erstellen wir eine Ausdrucksbaumstruktur, die dem folgenden 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 Lambda-Ausdrücke definieren. Hier sehen Sie den Code, der zum Erstellen derselben Funktionalität erforderlich ist. 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 im GitHub-Repository dotnet/docs anzeigen oder herunterladen. Experimentieren Sie selbst, indem Sie die Beispiele erstellen und ausführen.
Zuordnen von Codekonstrukten zu Ausdrücken
Im folgenden Codebeispiel wird eine Ausdrucksstruktur veranschaulicht, die den Lambda-Ausdruck num => num < 5
mithilfe der API darstellt.
// 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 Ausdrucksbaum-API unterstützt auch Zuweisungen und Kontrollflussausdrücke wie Schleifen, bedingte Blöcke und try-catch
Blöcke. Mithilfe der API können Sie Ausdrucksstrukturen erstellen, die komplexer sind als solche, die vom C#-Compiler aus Lambda-Ausdrücken erstellt werden können. 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 Generieren dynamischer Methoden mit Ausdrucksstrukturen in Visual Studio 2010, die auch für spätere Versionen von Visual Studio gelten.