Megosztás a következőn keresztül:


Kifejezésfák végrehajtása

A kifejezésfa egy olyan adatstruktúra, amely valamilyen kódot jelöl. Nem lefordított és végrehajtható kód. Ha egy kifejezésfával ábrázolt .NET-kódot szeretne végrehajtani, azt végrehajtható IL-utasításokká kell konvertálnia. A kifejezésfa végrehajtása visszaadhat egy értéket, vagy egyszerűen végrehajthat egy műveletet, például egy metódus meghívását.

Csak a lambda kifejezéseket ábrázoló kifejezésfák hajthatók végre. A lambda kifejezéseket ábrázoló kifejezésfák típusa LambdaExpression vagy Expression<TDelegate>típusa. A kifejezésfák végrehajtásához hívja meg a Compile metódust végrehajtható delegált létrehozásához, majd hívja meg a meghatalmazottat.

Feljegyzés

Ha a delegált típusa nem ismert, vagyis a lambda kifejezés típusa LambdaExpression nem Expression<TDelegate>, hívja meg a DynamicInvoke metódust a delegálton ahelyett, hogy közvetlenül invokálja.

Ha egy kifejezésfa nem lambda kifejezést jelöl, a metódus meghívásával Lambda<TDelegate>(Expression, IEnumerable<ParameterExpression>) létrehozhat egy új lambda kifejezést, amelynek törzse az eredeti kifejezésfával rendelkezik. Ezután végrehajthatja a lambda kifejezést az ebben a szakaszban korábban ismertetett módon.

Lambda-kifejezések függvényekhez

Bármilyen LambdaExpressiont vagy a LambdaExpressionból származó bármilyen típust végrehajtható IL-vé alakíthat át. Más kifejezéstípusok nem konvertálhatók közvetlenül kódmá. Ez a korlátozás a gyakorlatban kevés hatással van. A Lambda-kifejezések az egyetlen olyan kifejezéstípusok, amelyeket végrehajtható köztes nyelvre (IL) konvertálva szeretne végrehajtani. (Gondolja át, mit jelentene közvetlenül végrehajtani egy System.Linq.Expressions.ConstantExpression. Jelentene valami hasznosat?) Bármely kifejezésfa, amely egy System.Linq.Expressions.LambdaExpression, vagy egy származtatott LambdaExpression típus, átalakítható IL-vé. A kifejezéstípus System.Linq.Expressions.Expression<TDelegate> az egyetlen konkrét példa a .NET Core-kódtárakban. Olyan kifejezést jelöl, amely bármilyen delegált típushoz megfeleltethető. Mivel ez a típus delegált típusra van leképezve, a .NET megvizsgálhatja a kifejezést, és il-t hozhat létre egy megfelelő delegált számára, amely megfelel a lambda kifejezés aláírásának. A delegált típusa a kifejezés típusán alapul. Ha a delegált objektumot erősen gépelt módon szeretné használni, ismernie kell a visszatérési típust és az argumentumlistát. A LambdaExpression.Compile() metódus a típust Delegate adja vissza. A megfelelő delegálási típusra kell leadnia, hogy a fordítási idő eszközei ellenőrizhesse az argumentumlistát vagy a visszatérési típust.

A legtöbb esetben létezik egy egyszerű leképezés egy kifejezés és a hozzá tartozó meghatalmazott között. Az általuk Expression<Func<int>> képviselt kifejezésfa például egy ilyen típusú Func<int>delegálttá alakul. Bármely visszatérési típussal és argumentumlistával rendelkező lambda kifejezés esetében létezik egy delegált típus, amely az adott lambdakifejezés által képviselt végrehajtható kód céltípusa.

Ez a System.Linq.Expressions.LambdaExpression típus tartalmazza LambdaExpression.Compile a kifejezésfát végrehajtható kódmá alakító tagokat és LambdaExpression.CompileToMethod tagokat. A Compile metódus létrehoz egy meghatalmazottat. A CompileToMethod metódus frissíti az System.Reflection.Emit.MethodBuilder objektumot az IL-vel, amely a kifejezésfa lefordított kimenetét jelöli.

Fontos

CompileToMethodcsak .NET-keretrendszer érhető el, a .NET Core vagy a .NET 5 és újabb verziókban nem.

Igény szerint megadhat egy olyan objektumot System.Runtime.CompilerServices.DebugInfoGenerator is, amely megkapja a létrehozott delegált objektum hibakeresési információját. A DebugInfoGenerator szolgáltatás teljes körű hibakeresési információkat biztosít a létrehozott meghatalmazottról.

A kifejezést delegálttá alakítaná a következő kóddal:

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

Az alábbi példakód bemutatja a kifejezésfa fordításához és végrehajtásához használt konkrét típusokat.

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.

Az alábbi példakód bemutatja, hogyan hajthat végre egy olyan kifejezésfát, amely egy számot egy hatványra emel egy lambda-kifejezés létrehozásával és végrehajtásával. Megjelenik az eredmény, amely a teljesítményre emelt számot jelöli.

// 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

Végrehajtás és élettartamok

A kódot a híváskor LambdaExpression.Compile()létrehozott meghatalmazott meghívásával hajthatja végre. Az előző kód add.Compile()egy meghatalmazottat ad vissza. Ezt a delegáltat meghívja a kódot végrehajtó hívással func().

Ez a delegált a kifejezésfában lévő kódot jelöli. Megtarthatja a leírót a delegált számára, és később meg is hívhatja. Nem kell minden alkalommal lefordítania a kifejezésfát, amikor végre szeretné hajtani az általa képviselt kódot. (Ne feledje, hogy a kifejezésfák nem módosíthatók, és ugyanazon kifejezésfa összeállítása később létrehoz egy delegáltat, aki ugyanazt a kódot hajtja végre.)

Figyelemfelhívás

Ne hozzon létre kifinomultabb gyorsítótárazási mechanizmusokat a teljesítmény növelése érdekében a szükségtelen fordítási hívások elkerülése érdekében. Két tetszőleges kifejezésfa összehasonlítása annak megállapításához, hogy ugyanazt az algoritmust jelölik-e, időigényes művelet. A megtakarított számítási idő, amellyel elkerülheti a LambdaExpression.Compile() további hívásokat, valószínűleg több, mint amennyit a kód végrehajtásának ideje használ fel, amely meghatározza, hogy két különböző kifejezésfa ugyanazt a végrehajtható kódot eredményezi-e.

Figyelmeztetések

Lambda-kifejezés összeállítása delegáltnak, és a delegált meghívása az egyik legegyszerűbb művelet, amelyet egy kifejezésfával hajthat végre. Azonban még ezzel az egyszerű művelettel is tisztában kell lennie.

A Lambda-kifejezések lezárásokat hoznak létre a kifejezésben hivatkozott helyi változókon. Garantálnia kell, hogy a meghatalmazott részét képező változók a hívás Compilehelyén és az eredményként kapott meghatalmazott végrehajtásakor használhatók. A fordító biztosítja, hogy a változók hatókörben legyenek. Ha azonban a kifejezés egy implementált IDisposableváltozóhoz fér hozzá, lehetséges, hogy a kód a kifejezésfa birtokában is el tudja helyezni az objektumot.

Ez a kód például jól működik, mert int nem implementálja a következőt 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;
}

A meghatalmazott rögzítette a helyi változóra constantmutató hivatkozást. Ezt a változót később bármikor elérheti, amikor a függvényt CreateBoundFunc végrehajtja.

Vegye azonban figyelembe a következő (inkább kontrived) osztályt, amely implementálja System.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;
    }
}

Ha az alábbi kódban látható kifejezésben használja, a tulajdonság által Resource.Argument hivatkozott kód végrehajtásakor kap egy System.ObjectDisposedException értéket:

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

Az ebből a metódusból visszaadott meghatalmazott bezárult az constant objektum felett, amely el lett távolítva. (El lett adva, mert nyilatkozatban using deklarálták.)

Most, amikor végrehajtja az ebből a metódusból visszaadott meghatalmazottat, a végrehajtás helyén van egy ObjectDisposedException dobás.

Furcsának tűnik, hogy egy futásidejű hiba egy fordítási idő szerkezetét jelöli, de ez az a világ, amelyet a kifejezésfákkal való munka során ad meg.

Ennek a problémának számos permutációja van, ezért nehéz általános útmutatást nyújtani annak elkerüléséhez. Ügyeljen arra, hogy a kifejezések definiálásakor helyi változókhoz férhessen hozzá, és ügyeljen arra, hogy a nyilvános API-n keresztül visszaadott kifejezésfa létrehozásakor az aktuális objektum állapotának elérése (amelyet thisaz adott objektum jelöl) használjon.

A kifejezésben szereplő kód hivatkozhat más szerelvények metódusára vagy tulajdonságaira. A szerelvénynek elérhetőnek kell lennie a kifejezés definiálásakor, fordításakor és az eredményként kapott meghatalmazott meghívásakor. Olyan esettel ReferencedAssemblyNotFoundException találkoznak, ahol nincs jelen.

Összegzés

A lambda kifejezéseket ábrázoló kifejezésfák lefordíthatók egy végrehajtható delegált létrehozásához. A kifejezésfák egy mechanizmust biztosítanak a kifejezésfa által képviselt kód végrehajtásához.

A kifejezésfa azt a kódot jelöli, amely minden létrehozott szerkezethez végrehajtható. Mindaddig, amíg az a környezet, amelyben lefordítja és végrehajtja a kódot, megegyezik a kifejezés létrehozásához szükséges környezettel, minden a várt módon működik. Ha ez nem történik meg, a hibák kiszámíthatók, és a kód első tesztje során a fák kifejezés használatával jelennek meg.