Ausführen von AusdrucksbaumstrukturenExecuting Expression Trees

Vorheriges – Framework-Typen, die Ausdrucksbaumstrukturen unterstützenPrevious -- Framework Types Supporting Expression Trees

Eine Ausdrucksbaumstruktur ist eine Datenstruktur, die Code darstellt.An expression tree is a data structure that represents some code. Es ist ein nicht kompilierter und ausführbarer Code.It is not compiled and executable code. Wenn Sie den .NET-Code ausführen möchten, der durch eine Ausdrucksbaumstruktur dargestellt wird, müssen Sie ihn in ausführbare IL-Anweisungen konvertieren.If you want to execute the .NET code that is represented by an expression tree, you must convert it into executable IL instructions.

Lambdaausdrücke zu FunktionenLambda Expressions to Functions

Sie können jede LambdaExpression oder jeden Typ, der von LambdaExpression in eine ausführbare IL abgeleitet wurde, konvertieren.You can convert any LambdaExpression, or any type derived from LambdaExpression into executable IL. Andere Ausdruckstypen können nicht direkt in Code konvertiert werden.Other expression types cannot be directly converted into code. Diese Einschränkung wirkt sich kaum auf die Praxis aus.This restriction has little effect in practice. Lambdaausdrücke sind die einzigen Typen von Ausdrücken, die Sie ausführen möchten, indem Sie sie in eine ausführbare Zwischensprache (Intermediate Language, IL) konvertieren.Lambda expressions are the only types of expressions that you would want to execute by converting to executable intermediate language (IL). (Denken Sie darüber nach, was es bedeuten würde, eine ConstantExpression direkt auszuführen.(Think about what it would mean to directly execute a ConstantExpression. Würde es etwas Nützliches bedeuten?) Jede beliebige Ausdrucksbaumstruktur, die eine LamdbaExpression ist, oder ein abgeleiteter Typ von LambdaExpression, kann in eine IL konvertiert werden.Would it mean anything useful?) Any expression tree that is a LamdbaExpression, or a type derived from LambdaExpression can be converted to IL. Der Ausdruckstyp Expression<TDelegate> ist das einzige konkrete Beispiel in den .NET Core-Bibliotheken.The expression type Expression<TDelegate> is the only concrete example in the .NET Core libraries. Hiermit wird ein Ausdruck dargestellt, der jedem Delegattyp zugeordnet ist.It's used to represent an expression that maps to any delegate type. Da dieser Typ einem Delegattyp zugeordnet ist, kann .NET den Ausdruck untersuchen, und die IL für einen entsprechenden Delegaten generieren, der der Signatur des Lambdaausdrucks entspricht.Because this type maps to a delegate type, .NET can examine the expression, and generate IL for an appropriate delegate that matches the signature of the lambda expression.

In den meisten Fällen erstellt dies eine einfache Zuordnung zwischen einem Ausdruck und seinem entsprechenden Delegaten.In most cases, this creates a simple mapping between an expression, and its corresponding delegate. Angenommen, eine Ausdrucksbaumstruktur, die durch Expression<Func<int>> dargestellt wird, wird in einen Delegaten des Typs Func<int> konvertiert.For example, an expression tree that is represented by Expression<Func<int>> would be converted to a delegate of the type Func<int>. Für einen Lambdaausdruck mit beliebigem Rückgabetyp und Argumentliste besteht ein Delegattyp, der der Zieltyp für den ausführbaren Code ist, der von diesem Lambdaausdruck dargestellt wird.For a lambda expression with any return type and argument list, there exists a delegate type that is the target type for the executable code represented by that lamdba expression.

Der LamdbaExpression-Typ enthält Compile- und CompileToMethod-Member, die Sie verwenden würden, um eine Ausdrucksbaumstruktur in ausführbaren Code zu konvertieren.The LamdbaExpression type contains Compile and CompileToMethod members that you would use to convert an expression tree to executable code. Die Compile-Methode erstellt einen Delegaten.The Compile method creates a delegate. Die ConmpileToMethod-Methode aktualisiert ein MethodBuilder-Objekt mit der IL, das die kompilierte Ausgabe der Ausdrucksbaumstruktur darstellt.The ConmpileToMethod method updates a MethodBuilder object with the IL that represents the compiled output of the expression tree. Beachten Sie, dass CompileToMethod nur auf dem Desktop-Framework verfügbar ist, nicht auf dem .NET Core-Framework.Note that CompileToMethod is only available on the full desktop framework, not on the .NET Core framework.

Optional können Sie auch einen DebugInfoGenerator angeben, der das Symbol „Debuginformationen“ für das generierte Delegatobjekt empfängt.Optionally, you can also provide a DebugInfoGenerator that will receive the symbol debugging information for the generated delegate object. Dies ermöglicht es Ihnen, die Ausdrucksbaumstruktur in ein Delegatobjekt zu konvertieren, und über vollständige Debuginformationen über die generierten Delegate zu verfügen.This enables you to convert the expression tree into a delegate object, and have full debugging information about the generated delegate.

Sie würden einen Ausdruck mithilfe des folgenden Code in einen Delegaten konvertieren:You would convert an expression into a delegate using the following code:

Expression<Func<int>> add = () => 1 + 2;
var func = add.Compile(); // Create Delegate
var answer = func(); // Invoke Delegate
Console.WriteLine(answer);

Beachten Sie, dass der Delegattyp auf den Ausdruckstyp basiert.Notice that the delegate type is based on the expression type. Sie müssen den Rückgabetyp und die Argumentliste kennen, wenn Sie das Delegatobjekt mit strikter Typzuordnung verwenden möchten.You must know the return type and the argument list if you want to use the delegate object in a strongly typed manner. Die LambdaExpression.Compile()-Methode gibt den Delegate-Typ zurück.The LambdaExpression.Compile() method returns the Delegate type. Sie müssen es in den richtigen Delegattyp umwandeln, um Kompilierzeittools die Argumentliste des Rückgabetyps überprüfen zu lassen.You will have to cast it to the correct delegate type to have any compile-time tools check the argument list of return type.

Ausführung und LebensdauerExecution and Lifetimes

Sie führen den Code durch Aufrufen des Delegaten aus, den Sie beim Aufrufen von LamdbaExpression.Compile() erstellt haben.You execute the code by invoking the delegate created when you called LamdbaExpression.Compile(). Dies sehen Sie oben, wo add.Compile() einen Delegaten zurückgibt.You can see this above where add.Compile() returns a delegate. Rufen Sie diesen Delegaten durch Aufrufen von func() aus, der den Code ausführt.Invoking that delegate, by calling func() executes the code.

Dieser Delegat stellt den Code in der Ausdrucksbaumstruktur dar.That delegate represents the code in the expression tree. Sie können das Handle für diesen Delegaten beibehalten und es später aufrufen.You can retain the handle to that delegate and invoke it later. Sie müssen die Ausdrucksbaumstruktur nicht jedes Mal kompilieren, wenn Sie den Code ausführen möchten, den sie darstellt.You don't need to compile the expression tree each time you want to execute the code it represents. (Beachten Sie, dass Ausdrucksbaumstrukturen unveränderlich sind und dass das spätere Kompilieren der gleichen Ausdrucksbaumstruktur einen Delegaten erstellt, der den gleichen Code ausführt.)(Remember that expression trees are immutable, and compiling the same expression tree later will create a delegate that executes the same code.)

Ich rate Ihnen davon ab, zu versuchen, mehr anspruchsvollere Zwischenspeicherungsmechanismen zu erstellen, um die Leistungsfähigkeit durch Vermeiden unnötiger Kompilieraufrufe zu erhöhen.I will caution you against trying to create any more sophisticated caching mechanisms to increase performance by avoiding unnecessary compile calls. Das Vergleichen von zwei beliebigen Ausdrucksbaumstrukturen um festzustellen, ob sie den gleichen Algorithmus darstellen, wird auch zum Ausführen sehr zeitaufwändig sein.Comparing two arbitrary expression trees to determine if they represent the same algorithm will also be time consuming to execute. Sie werden wahrscheinlich feststellen, dass die Rechenzeit, die Sie durch Vermeiden zusätzlicher Aufrufe von LambdaExpression.Compile() sichern, durch die Zeit für das Ausführen von Code, der zwei verschiedene Ausdrucksbaumstrukturen bestimmt, die zum gleichen ausführbaren Code führen, mehr als verbraucht sein wird.You'll likely find that the compute time you save avoiding any extra calls to LambdaExpression.Compile() will be more than consumed by the time executing code that determines of two different expression trees result in the same executable code.

HinweiseCaveats

Das Kompilieren eines Lambdaausdrucks in einen Delegaten und das Aufrufen dieses Delegaten, ist einer der einfachsten Vorgänge, die Sie mit einer Ausdrucksbaumstruktur ausführen können.Compiling a lambda expression to a delegate and invoking that delegate is one of the simplest operations you can perform with an expression tree. Trotz dieses einfachen Vorgangs gibt es jedoch Hinweise, die Sie beachten müssen.However, even with this simple operation, there are caveats you must be aware of.

Lambdaausdrücke erstellen Closures über lokale Variablen, auf die im Ausdruck verwiesen wird.Lambda Expressions create closures over any local variables that are referenced in the expression. 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.You must guarantee that any variables that would be part of the delegate are usable at the location where you call Compile, and when you execute the resulting delegate.

Im Allgemeinen stellt der Compiler sicher, dass dies der Fall ist.In general, the compiler will ensure that this is true. Wenn jedoch der Ausdruck auf eine Variable zugreift, die IDisposable implementiert, ist es möglich, dass der Code das Objekt löschen kann, während es weiterhin in der Ausdrucksbaumstruktur aufrechterhalten wird.However, if your expression accesses a variable that implements IDisposable, it's possible that your code might dispose of the object while it is still held by the expression tree.

Angenommen, dieser Code funktioniert gut, da int nicht IDisposable implementiert:For example, this code works fine, because int does not implement IDisposable:

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.The delegate has captured a reference to the local variable constant. Auf diese Variable wird später zugegriffen, wenn die von CreateBoundFunc zurückgegebene Funktion ausgeführt wird.That variable is accessed at any time later, when the function returned by CreateBoundFunc executes.

Jedoch sollten Sie diese (ausgedachte) Klasse beachten, die IDisposable implementiert:However, consider this (rather contrived) class that implements IDisposable:

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;
    }
}

Wenn Sie es in einem Ausdruck verwenden, wie unten dargestellt, erhalten Sie eine ObjectDisposedException beim Ausführen von Code, auf den die Resource.Argument-Eigenschaft verweist:If you use it in an expression as shown below, you'll get an ObjectDisposedException when you execute the code referenced by the Resource.Argument property:

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.The delegate returned from this method has closed over the constant object, which has been disposed of. (Es wurde verworfen, da es in einer using-Anweisung deklariert wurde.)(It's been disposed, because it was declared in a using statement.)

Nun, wenn Sie den von dieser Methode zurückgegebenen Delegaten ausführen, müssen Sie eine ObjecctDisposedException haben, die zum Zeitpunkt der Ausführung ausgelöst wird.Now, when you execute the delegate returned from this method, you'll have a ObjecctDisposedException thrown at the point of execution.

Es scheint sonderbar, einen Laufzeitfehler zu haben, der ein Kompilierzeitkonstrukt darstellt, aber das ist nun mal gang und gäbe, wenn wir mit Ausdrucksbaumstrukturen arbeiten.It does seem strange to have a runtime error representing a compile-time construct, but that's the world we enter when we work with expression trees.

Es gibt viele Permutationen dieses Problem, daher ist es schwierig, eine allgemeine Anleitung zu bieten, um dies zu vermeiden.There are a lot of permutations of this problem, so it's hard to offer general guidance to avoid it. 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 werden kann.Be careful about accessing local variables when defining expressions, and be careful about accessing state in the current object (represented by this) when creating an expression tree that can be returned by a public API.

Der Code in einem Ausdruck kann auf Methoden oder Eigenschaften in anderen Assemblys verweisen.The code in your expression may reference methods or properties in other assemblies. Auf diese Assembly muss zugegriffen werden können, wenn der Ausdruck definiert ist, und wenn sie kompiliert wird und das resultierende Delegat aufgerufen wird.That assembly must be accessible when the expression is defined, and when it is compiled, and when the resulting delegate is invoked. Sie werden einer ReferencedAssemblyNotFoundException in Fällen begegnen, in denen es nicht vorhanden ist.You'll be met with a ReferencedAssemblyNotFoundException in cases where it is not present.

ZusammenfassungSummary

Ausdrucksbaumstrukturen, die Lambdaausdrücke darstellen, können kompiliert werden, um einen Delegaten zu erstellen, den Sie ausführen können.Expression Trees that represent lambda expressions can be compiled to create a delegate that you can execute. Dies bietet einen Mechanismus, um den durch eine Ausdrucksbaumstruktur dargestellten Code auszuführen.This provides one mechanism to execute the code represented by an expression tree.

Die Ausdrucksbaumstruktur stellt den Code dar, der für jedes angegebene Konstrukt ausgeführt werden würde, das Sie erstellen.The Expression Tree does represent the code that would execute for any given construct you create. 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.As long as the environment where you compile and execute the code matches the environment where you create the expression, everything works as expected. Wenn dies nicht der Fall ist, sind die Fehler sehr berechenbar, und sie werden in den ersten Tests von Code mithilfe der Ausdrucksbaumstrukturen abgefangen werden.When that doesn't happen, the errors are very predictable, and they will be caught in your first tests of any code using the expression trees.

Weiter – Interpretieren von AusdrückenNext -- Interpreting Expressions