Expressiestructuren uitvoeren

Een expressiestructuur is een gegevensstructuur die code vertegenwoordigt. Het is geen gecompileerde en uitvoerbare code. Als u de .NET-code wilt uitvoeren die wordt vertegenwoordigd door een expressiestructuur, moet u deze converteren naar uitvoerbare IL-instructies. Als u een expressiestructuur uitvoert, kan een waarde worden geretourneerd of kan er gewoon een actie worden uitgevoerd, zoals het aanroepen van een methode.

Alleen expressiestructuren die lambda-expressies vertegenwoordigen, kunnen worden uitgevoerd. Expressiestructuren die lambda-expressies vertegenwoordigen, zijn van het type LambdaExpression of Expression<TDelegate>. Als u deze expressiestructuren wilt uitvoeren, roept u de Compile methode aan om een uitvoerbare gemachtigde te maken en roept u de gemachtigde aan.

Notitie

Als het type van de gemachtigde niet bekend is, is de lambda-expressie van het type LambdaExpression en niet Expression<TDelegate>, roept u de DynamicInvoke methode aan voor de gemachtigde in plaats van deze rechtstreeks aan te roepen.

Als een expressiestructuur geen lambda-expressie vertegenwoordigt, kunt u een nieuwe lambda-expressie maken met de oorspronkelijke expressiestructuur als hoofdtekst door de methode aan Lambda<TDelegate>(Expression, IEnumerable<ParameterExpression>) te roepen. Vervolgens kunt u de lambda-expressie uitvoeren zoals eerder in deze sectie is beschreven.

Lambda-expressies voor functies

U kunt elke LambdaExpression of elk type dat is afgeleid van LambdaExpression, converteren naar uitvoerbare IL. Andere expressietypen kunnen niet rechtstreeks worden geconverteerd naar code. Deze beperking heeft weinig effect in de praktijk. Lambda-expressies zijn de enige typen expressies die u wilt uitvoeren door te converteren naar uitvoerbare tussenliggende taal (IL). (Denk na over wat het zou betekenen om een System.Linq.Expressions.ConstantExpression. Zou het iets nuttigs betekenen?) Elke expressiestructuur die een System.Linq.Expressions.LambdaExpressionof een type is dat is afgeleid van LambdaExpression , kan worden geconverteerd naar IL. Het expressietype System.Linq.Expressions.Expression<TDelegate> is het enige concrete voorbeeld in de .NET Core-bibliotheken. Het wordt gebruikt om een expressie weer te geven die is toegewezen aan elk type gemachtigde. Omdat dit type is toegewezen aan een gemachtigde, kan .NET de expressie onderzoeken en IL genereren voor een geschikte gemachtigde die overeenkomt met de handtekening van de lambda-expressie. Het type gemachtigde is gebaseerd op het expressietype. U moet het retourtype en de lijst met argumenten kennen als u het gedelegeerde-object op een sterk getypte manier wilt gebruiken. De LambdaExpression.Compile() methode retourneert het Delegate type. U moet het casten naar het juiste type gemachtigde om eventuele hulpprogramma's voor compileren de lijst met argumenten of het retourtype te controleren.

In de meeste gevallen bestaat er een eenvoudige toewijzing tussen een expressie en de bijbehorende gemachtigde. Een expressiestructuur die wordt vertegenwoordigd door Expression<Func<int>> , wordt bijvoorbeeld geconverteerd naar een gemachtigde van het type Func<int>. Voor een lambda-expressie met elk retourtype en een lijst met argumenten bestaat er een gemachtigdentype dat het doeltype is voor de uitvoerbare code die wordt vertegenwoordigd door die lambda-expressie.

Het System.Linq.Expressions.LambdaExpression type bevat LambdaExpression.Compile en LambdaExpression.CompileToMethod leden die u zou gebruiken om een expressiestructuur te converteren naar uitvoerbare code. Met de Compile methode maakt u een gemachtigde. Met CompileToMethod de methode wordt een System.Reflection.Emit.MethodBuilder object bijgewerkt met de IL die de gecompileerde uitvoer van de expressiestructuur vertegenwoordigt.

Belangrijk

CompileToMethod is alleen beschikbaar in .NET Framework, niet in .NET Core of .NET 5 en hoger.

U kunt desgewenst ook een System.Runtime.CompilerServices.DebugInfoGenerator object opgeven dat de informatie over het opsporen van symbolen voor het gegenereerde gedelegeerde-object ontvangt. Het DebugInfoGenerator biedt volledige informatie over foutopsporing over de gegenereerde gemachtigde.

U converteert een expressie naar een gemachtigde met behulp van de volgende code:

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

In het volgende codevoorbeeld ziet u de concrete typen die worden gebruikt bij het compileren en uitvoeren van een expressiestructuur.

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.

In het volgende codevoorbeeld ziet u hoe u een expressiestructuur uitvoert die een getal naar een macht verheffen door een lambda-expressie te maken en uit te voeren. Het resultaat, dat het getal weergeeft dat tot de macht is verheven, wordt weergegeven.

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

Uitvoering en levensduur

U voert de code uit door de gemachtigde aan te roepen die is gemaakt toen u aanroepde LambdaExpression.Compile(). Met de voorgaande code wordt add.Compile()een gemachtigde geretourneerd. U roept deze gemachtigde aan door aan te roepen func(), waarmee de code wordt uitgevoerd.

Deze gemachtigde vertegenwoordigt de code in de expressiestructuur. U kunt de ingang voor die gemachtigde behouden en later aanroepen. U hoeft de expressiestructuur niet telkens te compileren wanneer u de code wilt uitvoeren die deze vertegenwoordigt. (Houd er rekening mee dat expressiestructuren onveranderbaar zijn en dat bij het compileren van dezelfde expressiestructuur later een gemachtigde wordt gemaakt die dezelfde code uitvoert.)

Let op

Maak geen geavanceerdere cachingmechanismen om de prestaties te verbeteren door onnodige compileeroproepen te voorkomen. Het vergelijken van twee willekeurige expressiestructuren om te bepalen of ze hetzelfde algoritme vertegenwoordigen, is een tijdrovende bewerking. De rekentijd die u bespaart om extra aanroepen te LambdaExpression.Compile() vermijden, is waarschijnlijk meer dan wordt verbruikt door de tijd die code uitvoert die bepaalt of twee verschillende expressiestructuren resulteren in dezelfde uitvoerbare code.

Waarschuwingen

Het compileren van een lambda-expressie aan een gemachtigde en het aanroepen van die gemachtigde is een van de eenvoudigste bewerkingen die u met een expressiestructuur kunt uitvoeren. Maar zelfs met deze eenvoudige bewerking zijn er opmerkingen waar u rekening mee moet houden.

Lambda-expressies maken sluitingen voor lokale variabelen waarnaar in de expressie wordt verwezen. U moet garanderen dat variabelen die deel uitmaken van de gemachtigde, bruikbaar zijn op de locatie waar u aanroept Compileen wanneer u de resulterende gemachtigde uitvoert. De compiler zorgt ervoor dat variabelen binnen het bereik vallen. Als uw expressie echter toegang heeft tot een variabele die wordt geïmplementeerd IDisposable, is het mogelijk dat uw code het object kan verwijderen terwijl het nog steeds wordt bewaard door de expressiestructuur.

Deze code werkt bijvoorbeeld prima, omdat int het niet wordt geïmplementeerd 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;
}

De gedelegeerde heeft een verwijzing naar de lokale variabele constantvastgelegd. Deze variabele wordt later geopend wanneer de functie wordt geretourneerd door CreateBoundFunc uitvoering.

Houd echter rekening met de volgende (eerder contrived) klasse die implementeert 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;
    }
}

Als u deze gebruikt in een expressie zoals wordt weergegeven in de volgende code, krijgt u een System.ObjectDisposedException opdracht wanneer u de code uitvoert waarnaar wordt verwezen door de Resource.Argument eigenschap:

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

De gedelegeerde die door deze methode wordt geretourneerd, is gesloten over het constant object dat is verwijderd. (Het is verwijderd omdat het in een using instructie is gedeclareerd.)

Wanneer u nu de gedelegeerde uitvoert die is geretourneerd door deze methode, hebt u een ObjectDisposedException fout gegenereerd op het moment van uitvoering.

Het lijkt vreemd om een runtime-fout te hebben die een compileertijdconstructie vertegenwoordigt, maar dat is de wereld die u invoert wanneer u met expressiestructuren werkt.

Er zijn talloze permutaties van dit probleem, dus het is moeilijk om algemene richtlijnen te bieden om dit te voorkomen. Wees voorzichtig met het openen van lokale variabelen bij het definiëren van expressies en wees voorzichtig met het openen van de status in het huidige object (vertegenwoordigd door this) bij het maken van een expressiestructuur die wordt geretourneerd via een openbare API.

De code in uw expressie kan verwijzen naar methoden of eigenschappen in andere assembly's. Deze assembly moet toegankelijk zijn wanneer de expressie is gedefinieerd, wanneer deze wordt gecompileerd en wanneer de resulterende gemachtigde wordt aangeroepen. U wordt ontmoet met een ReferencedAssemblyNotFoundException in gevallen waarin deze niet aanwezig is.

Samenvatting

Expressiestructuren die lambda-expressies vertegenwoordigen, kunnen worden gecompileerd om een gemachtigde te maken die u kunt uitvoeren. Expressiestructuren bieden één mechanisme voor het uitvoeren van de code die wordt vertegenwoordigd door een expressiestructuur.

De expressiestructuur vertegenwoordigt de code die wordt uitgevoerd voor een bepaalde constructie die u maakt. Zolang de omgeving waarin u de code compileert en uitvoert, overeenkomt met de omgeving waarin u de expressie maakt, werkt alles zoals verwacht. Als dat niet gebeurt, zijn de fouten voorspelbaar en worden ze in uw eerste tests van code opgevangen met behulp van de expressiestructuren.