Delen via


Expressiestructuren bouwen

De C#-compiler heeft alle expressiestructuren gemaakt die u tot nu toe hebt gezien. U hebt een lambda-expressie gemaakt die is toegewezen aan een variabele die is getypt als een Expression<Func<T>> of ander vergelijkbaar type. Voor veel scenario's bouwt u tijdens runtime een expressie in het geheugen.

Expressiestructuren zijn onveranderbaar. Onveranderbaar betekent dat u de boom van de bladeren tot aan de wortel moet bouwen. De API's die u gebruikt om expressiestructuren te bouwen, weerspiegelen dit feit: de methoden die u gebruikt om een knooppunt te bouwen, nemen alle onderliggende elementen als argumenten. Laten we een paar voorbeelden bekijken om u de technieken te laten zien.

Knooppunten maken

U begint met de toevoegingsexpressie waarmee u in deze secties hebt gewerkt:

Expression<Func<int>> sum = () => 1 + 2;

Als u die expressiestructuur wilt maken, maakt u eerst de leaf-knooppunten. De leaf-knooppunten zijn constanten. Gebruik de Constant methode om de knooppunten te maken:

var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));

Bouw vervolgens de expressie voor optellen:

var addition = Expression.Add(one, two);

Zodra u de optelexpressie hebt gemaakt, maakt u de lambda-expressie:

var lambda = Expression.Lambda(addition);

Deze lambda-expressie bevat geen argumenten. Verderop in deze sectie ziet u hoe u argumenten kunt toewijzen aan parameters en complexere expressies kunt maken.

Voor expressies zoals deze kunt u alle aanroepen combineren tot één instructie:

var lambda2 = Expression.Lambda(
    Expression.Add(
        Expression.Constant(1, typeof(int)),
        Expression.Constant(2, typeof(int))
    )
);

Een boom bouwen

In de vorige sectie werden de basisbeginselen getoond van het bouwen van een expressiestructuur in het geheugen. Complexere structuren betekenen over het algemeen meer knooppunttypen en meer knooppunten in de structuur. Laten we nog een voorbeeld bekijken en nog twee knooppunttypen weergeven die u doorgaans bouwt wanneer u expressiestructuren maakt: de argumentknooppunten en methode-aanroepknooppunten. Laten we een expressiestructuur maken om deze expressie te maken:

Expression<Func<double, double, double>> distanceCalc =
    (x, y) => Math.Sqrt(x * x + y * y);

U begint met het maken van parameterexpressies voor x en y:

var xParameter = Expression.Parameter(typeof(double), "x");
var yParameter = Expression.Parameter(typeof(double), "y");

Als u de expressies voor vermenigvuldigen en optellen maakt, volgt u het patroon dat u al hebt gezien:

var xSquared = Expression.Multiply(xParameter, xParameter);
var ySquared = Expression.Multiply(yParameter, yParameter);
var sum = Expression.Add(xSquared, ySquared);

Vervolgens moet u een methode-aanroepexpressie maken voor de aanroep naar Math.Sqrt.

var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) }) ?? throw new InvalidOperationException("Math.Sqrt not found!");
var distance = Expression.Call(sqrtMethod, sum);

De GetMethod aanroep kan worden geretourneerd null als de methode niet wordt gevonden. Waarschijnlijk is dat omdat u de naam van de methode verkeerd hebt gespeld. Anders kan dit betekenen dat de vereiste assembly niet is geladen. Ten slotte plaatst u de methode-aanroep in een lambda-expressie en zorgt u ervoor dat u de argumenten definieert voor de lambda-expressie:

var distanceLambda = Expression.Lambda(
    distance,
    xParameter,
    yParameter);

In dit gecompliceerdere voorbeeld ziet u een aantal meer technieken die u vaak nodig hebt om expressiestructuren te maken.

Eerst moet u de objecten maken die parameters of lokale variabelen vertegenwoordigen voordat u ze gebruikt. Zodra u deze objecten hebt gemaakt, kunt u ze overal in de expressiestructuur gebruiken.

Ten tweede moet u een subset van de Reflectie-API's gebruiken om een System.Reflection.MethodInfo object te maken, zodat u een expressiestructuur kunt maken voor toegang tot die methode. U moet uzelf beperken tot de subset van de Reflectie-API's die beschikbaar zijn op het .NET Core-platform. Deze technieken breiden zich opnieuw uit naar andere expressiestructuren.

Uitgebreide code bouwen

U bent niet beperkt in wat u kunt bouwen met behulp van deze API's. Hoe ingewikkelder de expressiestructuur die u wilt bouwen, hoe moeilijker de code is om te beheren en te lezen.

Laten we een expressiestructuur bouwen die het equivalent is van deze code:

Func<int, int> factorialFunc = (n) =>
{
    var res = 1;
    while (n > 1)
    {
        res = res * n;
        n--;
    }
    return res;
};

De voorgaande code heeft de expressiestructuur niet gebouwd, maar gewoon de gemachtigde. Met behulp van de Expression klasse kunt u geen lambdas-instructie bouwen. Hier volgt de code die nodig is om dezelfde functionaliteit te bouwen. Er is geen API voor het bouwen van een while lus. U moet in plaats daarvan een lus bouwen die een voorwaardelijke test bevat en een labeldoel om de lus te verbreken.

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

De code voor het bouwen van de expressiestructuur voor de factoriële functie is iets langer, ingewikkelder en is vol met labels en onderbrekingsinstructies en andere elementen die u in onze dagelijkse codetaken wilt vermijden.

Voor deze sectie hebt u code geschreven om elk knooppunt in deze expressiestructuur te bezoeken en informatie uit te schrijven over de knooppunten die in dit voorbeeld zijn gemaakt. U kunt de voorbeeldcode bekijken of downloaden in de GitHub-opslagplaats dotnet/docs. Experimenteer zelf door de voorbeelden te bouwen en uit te voeren.

Codeconstructies toewijzen aan expressies

In het volgende codevoorbeeld ziet u een expressiestructuur die de lambda-expressie num => num < 5 vertegenwoordigt met behulp van de API.

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

De API voor expressiestructuren ondersteunt ook toewijzingen en controlestroomexpressies zoals lussen, voorwaardelijke blokken en try-catch blokken. Met behulp van de API kunt u expressiestructuren maken die complexer zijn dan de structuren die kunnen worden gemaakt op basis van lambda-expressies door de C#-compiler. In het volgende voorbeeld ziet u hoe u een expressiestructuur maakt waarmee de faculteit van een getal wordt berekend.

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

Zie Dynamische methoden genereren met expressiestructuren in Visual Studio 2010, die ook van toepassing zijn op latere versies van Visual Studio voor meer informatie.