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.
Eine Ausdrucksstruktur ist eine Datenstruktur, die Code darstellt. Es handelt sich nicht um kompilierten und ausführbaren Code. Wenn Sie den .NET-Code ausführen möchten, der durch eine Ausdrucksstruktur dargestellt wird, müssen Sie ihn in ausführbare IL-Anweisungen konvertieren. Das Ausführen eines Ausdrucksbaumes gibt möglicherweise einen Wert zurück, oder es führt einfach eine Aktion aus, z. B. das Aufrufen einer Methode.
Nur Ausdrucksbäume, die Lambda-Ausdrücke darstellen, können ausgeführt werden. Ausdrucksbaumstrukturen, die Lambdaausdrücke darstellen, sind vom Typ LambdaExpression oder Expression<TDelegate>. Um diese Ausdrucksbaumstruktur auszuführen, rufen Sie die Compile-Methode auf, um einen ausführbaren Delegaten zu erstellen und diesen anschließend aufzurufen.
Hinweis
Wenn der Typ des Delegaten nicht bekannt ist, d. h. der Lambda-Ausdruck vom Typ LambdaExpression und nicht Expression<TDelegate> ist, rufen Sie die Methode DynamicInvoke für den Delegaten auf, anstatt ihn direkt aufzurufen.
Wenn eine Ausdrucksstruktur keinen Lambda-Ausdruck darstellt, können Sie einen neuen Lambda-Ausdruck erstellen, der die ursprüngliche Ausdrucksstruktur als Textkörper aufweist, indem Sie die Lambda<TDelegate>(Expression, IEnumerable<ParameterExpression>) Methode aufrufen. Anschließend können Sie den Lambda-Ausdruck wie weiter oben in diesem Abschnitt beschrieben ausführen.
Lambda-Ausdrücke für Funktionen
Sie können jeden LambdaExpression-Typ oder einen beliebigen typ, der von LambdaExpression abgeleitet wurde, in ausführbare IL konvertieren. Andere Ausdruckstypen können nicht direkt in Code konvertiert werden. Diese Einschränkung hat in der Praxis wenig Wirkung. Lambda-Ausdrücke sind die einzigen Arten von Ausdrücken, die Sie ausführen möchten, indem Sie in ausführbare Zwischensprache (IL) konvertieren. (Überlegen Sie, was es bedeuten würde, eine System.Linq.Expressions.ConstantExpression direkt auszuführen. Wäre das sinnvoll?) Jede Ausdrucksstruktur, die eine System.Linq.Expressions.LambdaExpressionoder ein von LambdaExpression
abgeleiteter Typ ist, kann in IL konvertiert werden. Der Ausdruckstyp System.Linq.Expressions.Expression<TDelegate> ist das einzige konkrete Beispiel in den .NET Core-Bibliotheken. Hiermit wird ein Ausdruck dargestellt, der jedem Delegattyp zugeordnet ist. Da dieser Typ einem Delegattyp zugeordnet ist, kann .NET den Ausdruck untersuchen und il für einen geeigneten Delegaten generieren, der der Signatur des Lambda-Ausdrucks entspricht. Der Delegattyp basiert auf dem Ausdruckstyp. Sie müssen den Rückgabetyp und die Argumentliste kennen, wenn Sie das Delegatobjekt mit strikter Typzuordnung verwenden möchten. Die LambdaExpression.Compile()
Methode gibt den Delegate
Typ zurück. Sie müssen ihn in den richtigen Delegattyp umwandeln, um Kompilierzeittools die Argumentliste oder den Rückgabetyp überprüfen zu lassen.
In den meisten Fällen ist eine einfache Zuordnung zwischen einem Ausdruck und seinem entsprechenden Delegaten vorhanden. Angenommen, eine durch Expression<Func<int>>
dargestellte Ausdrucksbaumstruktur wird in einen Delegaten des Typs Func<int>
konvertiert. Für einen Lambda-Ausdruck mit jeder Rückgabetyp- und Argumentliste gibt es einen Delegatentyp, der den Zieltyp für den ausführbaren Code darstellt, der durch diesen Lambda-Ausdruck dargestellt wird.
Der System.Linq.Expressions.LambdaExpression Typ enthält LambdaExpression.Compile und LambdaExpression.CompileToMethod Elemente, die Sie zum Konvertieren einer Ausdrucksstruktur in ausführbaren Code verwenden würden. Die Compile
Methode erstellt einen Delegaten. Die CompileToMethod
-Methode aktualisiert ein System.Reflection.Emit.MethodBuilder-Objekt mit der IL, das die kompilierte Ausgabe der Ausdrucksbaumstruktur darstellt.
Von Bedeutung
CompileToMethod
ist nur in .NET Framework verfügbar, nicht in .NET Core oder .NET 5 und höher.
Optional können Sie auch ein System.Runtime.CompilerServices.DebugInfoGenerator Objekt bereitstellen, das die Symboldebugginginformationen für das generierte Delegatobjekt empfängt. Der DebugInfoGenerator
stellt vollständige Debuginformationen zum generierten Delegaten bereit.
Sie würden einen Ausdruck mithilfe des folgenden Code in einen Delegaten konvertieren:
Expression<Func<int>> add = () => 1 + 2;
var func = add.Compile(); // Create Delegate
var answer = func(); // Invoke Delegate
Console.WriteLine(answer);
Im folgenden Codebeispiel werden die konkreten Typen gezeigt, die beim Kompilieren und Ausführen eines Ausdrucksbaums verwendet werden.
Expression<Func<int, bool>> expr = num => num < 5;
// Compiling the expression tree into a delegate.
Func<int, bool> result = expr.Compile();
// Invoking the delegate and writing the result to the console.
Console.WriteLine(result(4));
// Prints True.
// You can also use simplified syntax
// to compile and run an expression tree.
// The following line can replace two previous statements.
Console.WriteLine(expr.Compile()(4));
// Also prints True.
Im folgenden Codebeispiel wird veranschaulicht, wie eine Ausdrucksstruktur, die das Erhöhen einer Zahl auf eine Potenz darstellt, durch Erstellen und Ausführen eines Lambda-Ausdrucks ausgeführt wird. Das Ergebnis, das die potenzierte Zahl darstellt, wird angezeigt.
// The expression tree to execute.
BinaryExpression be = Expression.Power(Expression.Constant(2d), Expression.Constant(3d));
// Create a lambda expression.
Expression<Func<double>> le = Expression.Lambda<Func<double>>(be);
// Compile the lambda expression.
Func<double> compiledExpression = le.Compile();
// Execute the lambda expression.
double result = compiledExpression();
// Display the result.
Console.WriteLine(result);
// This code produces the following output:
// 8
Ausführung und Lebensdauer
Sie führen den Code durch Aufrufen des Delegaten aus, den Sie beim Aufrufen von LambdaExpression.Compile()
erstellt haben. Der vorangehende Code , add.Compile()
gibt einen Delegaten zurück. Sie rufen diesen Delegaten auf, indem Sie func()
aufrufen, wodurch der Code ausgeführt wird.
Dieser Delegat stellt den Code im Ausdrucksbaum dar. Sie können das Handle für diesen Delegaten beibehalten und es später aufrufen. Sie müssen den Ausdrucksbaum nicht jedes Mal kompilieren, wenn Sie den dargestellten Code ausführen möchten. (Denken Sie daran, dass Ausdrucksbäume unveränderlich sind und die spätere Kompilierung desselben Ausdrucksbaums einen Delegaten erstellt, der denselben Code ausführt.)
Vorsicht
Erstellen Sie keine komplexeren Cachingmechanismen, um die Leistung zu steigern, indem Sie unnötige Kompilierungsaufrufe vermeiden. Der Vergleich von zwei beliebigen Ausdrucksstrukturen, um festzustellen, ob sie denselben Algorithmus darstellen, ist ein zeitaufwendiger Vorgang. Die Rechenzeit, die Sie sparen, um zusätzliche Aufrufe zu LambdaExpression.Compile()
zu vermeiden, ist wahrscheinlich geringer als die durch die Ausführung des Codes verbrauchte Zeit, der bestimmt, ob zwei unterschiedliche Ausdrucksstrukturen zu demselben ausführbaren Code führen.
Vorbehalte
Das Kompilieren eines Lambda-Ausdrucks zu einem Delegat und das Aufrufen dieses Delegats ist eine der einfachsten Vorgänge, die Sie mit einem Expression Tree ausführen können. Aber auch bei diesem einfachen Vorgang gibt es Vorbehalte, die Sie kennen müssen.
Lambda-Ausdrücke erstellen Schließungen über alle lokalen Variablen, auf die im Ausdruck verwiesen wird. Sie müssen sicherstellen, dass alle Variablen, die Teil des Delegaten wären, am Speicherort verwendet werden können, wo Sie Compile
aufrufen sowie beim Ausführen des resultierenden Delegats. Der Compiler stellt sicher, dass sich Variablen im Bereich befinden. Wenn Ihr Ausdruck jedoch auf eine Variable zugreift, die IDisposable
implementiert, ist es möglich, dass Ihr Code das Objekt verwirft, während es noch vom Ausdrucksbaum gehalten wird.
Dieser Code funktioniert z. B. einwandfrei, da int
IDisposable
nicht implementiert.
private static Func<int, int> CreateBoundFunc()
{
var constant = 5; // constant is captured by the expression tree
Expression<Func<int, int>> expression = (b) => constant + b;
var rVal = expression.Compile();
return rVal;
}
Der Delegat hat einen Verweis auf die lokale Variable constant
erfasst. Auf diese Variable wird zu einem späteren Zeitpunkt zugegriffen, wenn die Funktion, die von CreateBoundFunc
zurückgegeben wird, ausgeführt wird.
Beachten Sie jedoch die folgende (eher konstruierte) Klasse, die System.IDisposable implementiert.
public class Resource : IDisposable
{
private bool _isDisposed = false;
public int Argument
{
get
{
if (!_isDisposed)
return 5;
else throw new ObjectDisposedException("Resource");
}
}
public void Dispose()
{
_isDisposed = true;
}
}
Bei Verwendung in einem Ausdruck, wie im folgenden Code dargestellt, erhalten Sie eine System.ObjectDisposedException beim Ausführen von Code, auf den die Resource.Argument
-Eigenschaft verweist:
private static Func<int, int> CreateBoundResource()
{
using (var constant = new Resource()) // constant is captured by the expression tree
{
Expression<Func<int, int>> expression = (b) => constant.Argument + b;
var rVal = expression.Compile();
return rVal;
}
}
Der Delegat, der von dieser Methode zurückgegeben wurde, wurde über das constant
-Objekt geschlossen, das verworfen wurde. (Es wurde verworfen, da es in einer using
-Anweisung deklariert wurde.)
Wenn Sie nun den von dieser Methode zurückgegebenen Delegaten ausführen, wird zum Zeitpunkt der Ausführung eine ObjectDisposedException
ausgelöst.
Es erscheint sonderbar, dass ein Laufzeitfehler ein Kompilierzeitkonstrukt darstellt, aber das ist nun mal gang und gäbe, wenn Sie mit Ausdrucksbaumstrukturen arbeiten.
Es gibt zahlreiche Permutationen dieses Problems, daher ist es schwierig, allgemeine Anleitungen zu bieten, um es zu vermeiden. Seien Sie mit dem Zugriff auf lokale Variablen beim Definieren von Ausdrücken vorsichtig. Dies gilt auch beim Zugreifen auf Status im aktuellen Objekt (dargestellt durch this
), wenn Sie eine Ausdrucksbaumstruktur erstellen, die durch eine öffentliche API zurückgegeben wird.
Der Code in Ihrem Ausdruck kann auf Methoden oder Eigenschaften in anderen Assemblys verweisen. Der Zugriff auf diese Assembly muss möglich sein, wenn der Ausdruck definiert ist, wenn sie kompiliert wird und der resultierende Delegat aufgerufen wird. In Fällen, wo dies nicht der Fall ist, tritt eine ReferencedAssemblyNotFoundException
auf.
Zusammenfassung
Ausdrucksbäume, die Lambda-Ausdrücke repräsentieren, können kompiliert werden, um einen Delegaten zu erzeugen, den Sie ausführen können. Ausdrucksbäume bieten einen Mechanismus zur Ausführung des Codes, der durch einen Ausdrucksbaum dargestellt wird.
Die Ausdrucksbaumstruktur stellt den Code dar, der für jedes angegebene Konstrukt ausgeführt werden würde, das Sie erstellen. Solange die Umgebung, in der Sie den Code kompilieren und ausführen, der Umgebung entspricht, in der Sie den Ausdruck erstellen, funktioniert alles wie erwartet. Wenn dies nicht der Fall ist, sind die Fehler vorhersehbar, und sie werden bei den ersten Tests jeglichen Codes, der Ausdrucksbäume verwendet, abgefangen.