Dela via


Skapa uttrycksträd

C#-kompilatorn skapade alla uttrycksträd som du har sett hittills. Du skapade ett lambda-uttryck som tilldelats en variabel som skrivits som en Expression<Func<T>> eller någon liknande typ. I många scenarier skapar du ett uttryck i minnet vid körning.

Uttrycksträd är oföränderliga. Att vara oföränderlig innebär att du måste bygga trädet från bladen upp till roten. De API:er som du använder för att skapa uttrycksträd återspeglar detta faktum: De metoder du använder för att skapa en nod tar alla dess underordnade som argument. Låt oss gå igenom några exempel för att visa dig teknikerna.

Skapa noder

Du börjar med det additionsuttryck som du har arbetat med i de här avsnitten:

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

Om du vill konstruera uttrycksträdet skapar du först lövnoderna. Lövnoderna är konstanter. Constant Använd metoden för att skapa noderna:

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

Skapa sedan additionsuttrycket:

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

När du har skapat additionsuttrycket skapar du lambda-uttrycket:

var lambda = Expression.Lambda(addition);

Det här lambda-uttrycket innehåller inga argument. Senare i det här avsnittet ser du hur du mappar argument till parametrar och skapar mer komplicerade uttryck.

För uttryck som den här kan du kombinera alla anrop till en enda instruktion:

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

Skapa ett träd

I föregående avsnitt visades grunderna för att skapa ett uttrycksträd i minnet. Mer komplexa träd innebär i allmänhet fler nodtyper och fler noder i trädet. Nu ska vi gå igenom ytterligare ett exempel och visa ytterligare två nodtyper som du vanligtvis skapar när du skapar uttrycksträd: argumentnoderna och metodanropsnoderna. Nu ska vi skapa ett uttrycksträd för att skapa det här uttrycket:

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

Du börjar med att skapa parameteruttryck för x och y:

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

När du skapar multiplikations- och additionsuttrycken följer du det mönster som du redan har sett:

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

Därefter måste du skapa ett metodanropsuttryck för anropet till Math.Sqrt.

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

Anropet GetMethod kan returneras null om metoden inte hittas. Troligtvis beror det på att du har felstavat metodnamnet. Annars kan det innebära att den nödvändiga sammansättningen inte läses in. Slutligen placerar du metodanropet i ett lambda-uttryck och ser till att definiera argumenten till lambda-uttrycket:

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

I det här mer komplicerade exemplet ser du några fler tekniker som du ofta behöver för att skapa uttrycksträd.

Först måste du skapa de objekt som representerar parametrar eller lokala variabler innan du använder dem. När du har skapat dessa objekt kan du använda dem i uttrycksträdet var du än behöver.

För det andra måste du använda en delmängd av reflektions-API:erna för att skapa ett System.Reflection.MethodInfo objekt så att du kan skapa ett uttrycksträd för att komma åt den metoden. Du måste begränsa dig till delmängden av de reflektions-API:er som är tillgängliga på .NET Core-plattformen. De här teknikerna omfattar även andra uttrycksträd.

Skapa kod på djupet

Du är inte begränsad till vad du kan skapa med hjälp av dessa API:er. Men ju mer komplicerat uttrycksträd du vill skapa, desto svårare är koden att hantera och läsa.

Nu ska vi skapa ett uttrycksträd som motsvarar den här koden:

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

Föregående kod har inte byggt uttrycksträdet, utan bara ombudet. Med hjälp av Expression klassen kan du inte skapa instruktionslamdas. Här är den kod som krävs för att skapa samma funktioner. Det finns inget API för att skapa en while loop, utan du måste skapa en loop som innehåller ett villkorstest och ett etikettmål för att bryta ut ur loopen.

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

Koden för att skapa uttrycksträdet för den faktoriella funktionen är ganska lite längre, mer komplicerad, och den är full av etiketter och brytsatser och andra element som du vill undvika i våra dagliga kodningsuppgifter.

I det här avsnittet skrev du kod för att besöka varje nod i det här uttrycksträdet och skriva ut information om de noder som skapas i det här exemplet. Du kan visa eller ladda ned exempelkoden på GitHub-lagringsplatsen dotnet/docs. Experimentera själv genom att skapa och köra exemplen.

Mappa kodkonstruktioner till uttryck

I följande kodexempel visas ett uttrycksträd som representerar lambda-uttrycket num => num < 5 med hjälp av API:et.

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

API:et för uttrycksträd stöder även tilldelningar och kontrollflödesuttryck som loopar, villkorsblock och try-catch block. Med hjälp av API:et kan du skapa uttrycksträd som är mer komplexa än de som kan skapas från lambda-uttryck av C#-kompilatorn. I följande exempel visas hur du skapar ett uttrycksträd som beräknar faktorn för ett tal.

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

Mer information finns i Generera dynamiska metoder med uttrycksträd i Visual Studio 2010, som även gäller för senare versioner av Visual Studio.