Sdílet prostřednictvím


Vytváření stromů výrazů

Kompilátor jazyka C# vytvořil všechny stromy výrazů, které jste zatím viděli. Vytvořili jste výraz lambda přiřazený proměnné, který je zadaný jako Expression<Func<T>> nebo nějaký podobný typ. V mnoha scénářích sestavíte výraz v paměti za běhu.

Stromy výrazů jsou neměnné. Neměnné znamená, že je nutné vytvořit strom z listů až do kořene. Rozhraní API, která používáte k sestavení stromů výrazů, odrážejí tento fakt: Metody, které používáte k sestavení uzlu, přebírají všechny podřízené položky jako argumenty. Pojďme si projít několik příkladů, abychom vám ukázali techniky.

Vytváření uzlů

Začněte výrazem sčítání, se kterým jste pracovali v těchto částech:

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

Pokud chcete tento strom výrazů vytvořit, nejprve vytvoříte uzly typu list. Uzly typu list jsou konstanty. Constant Pomocí metody vytvořte uzly:

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

Dále sestavte výraz sčítání:

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

Po vytvoření výrazu sčítání vytvoříte výraz lambda:

var lambda = Expression.Lambda(addition);

Tento výraz lambda neobsahuje žádné argumenty. Dále v této části se dozvíte, jak mapovat argumenty na parametry a vytvářet složitější výrazy.

U výrazů, jako je tento, můžete zkombinovat všechna volání do jednoho příkazu:

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

Vytvoření stromu

Předchozí část ukázala základy vytváření stromu výrazů v paměti. Složitější stromy obecně znamenají více typů uzlů a více uzlů ve stromu. Pojďme si projít jeden další příklad a zobrazit dva další typy uzlů, které obvykle vytváříte při vytváření stromů výrazů: uzly argumentů a uzly volání metody. Pojďme vytvořit strom výrazů pro vytvoření tohoto výrazu:

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

Začnete vytvořením výrazů parametrů pro x a y:

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

Vytvoření výrazů násobení a sčítání se řídí vzorem, který jste už viděli:

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

Dále je nutné vytvořit výraz volání metody pro volání Math.Sqrtvolání .

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

Volání GetMethod by se mohlo vrátit null , pokud metoda nebyla nalezena. Pravděpodobně je to proto, že jste chybně zadali název metody. Jinak by to mohlo znamenat, že požadované sestavení není načteno. Nakonec volání metody vložíte do výrazu lambda a nezapomeňte definovat argumenty výrazu lambda:

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

V tomto složitějším příkladu vidíte několik dalších technik, které často potřebujete vytvořit stromy výrazů.

Nejprve je potřeba vytvořit objekty, které představují parametry nebo místní proměnné, než je použijete. Jakmile tyto objekty vytvoříte, můžete je ve stromu výrazů použít všude, kde potřebujete.

Zadruhé je potřeba použít podmnožinu rozhraní API Reflexe ion k vytvoření System.Reflection.MethodInfo objektu, abyste mohli vytvořit strom výrazů pro přístup k této metodě. Musíte se omezit na podmnožinu rozhraní API Reflexe ion, která jsou k dispozici na platformě .NET Core. Tyto techniky se opět rozšiřují na další stromy výrazů.

Podrobný build kódu

Nejste omezeni tím, co můžete vytvářet pomocí těchto rozhraní API. Složitější strom výrazů, který chcete sestavit, je ale obtížnější kód spravovat a číst.

Pojďme vytvořit strom výrazu, který je ekvivalentem tohoto kódu:

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

Předchozí kód nevytvořil strom výrazů, ale jednoduše delegáta. Expression Pomocí třídy nemůžete sestavit výrazy lambda. Tady je kód, který se vyžaduje k sestavení stejné funkce. Pro sestavení while smyčky neexistuje rozhraní API, místo toho je potřeba vytvořit smyčku, která obsahuje podmíněný test, a cíl popisku pro přerušení smyčky.

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

Kód pro sestavení stromu výrazů pro faktoriální funkci je poměrně delší, složitější a je plýtvá popisky a break příkazy a další prvky, kterým byste se chtěli vyhnout v každodenních úkolech kódování.

V této části jste napsali kód, který navštíví každý uzel v tomto stromu výrazů a zapíše informace o uzlech vytvořených v této ukázce. Ukázkový kód si můžete prohlédnout nebo stáhnout v úložišti dotnet/docs na GitHubu. Experimentujte pro sebe tím, že sestavíte a spustíte ukázky.

Mapování konstruktorů kódu na výrazy

Následující příklad kódu ukazuje strom výrazu, který představuje výraz num => num < 5 lambda pomocí rozhraní 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 });

Rozhraní API stromy výrazů také podporuje přiřazení a výrazy toku řízení, jako jsou smyčky, podmíněné bloky a try-catch bloky. Pomocí rozhraní API můžete vytvořit stromy výrazů, které jsou složitější než ty, které je možné vytvořit z výrazů lambda kompilátorem jazyka C#. Následující příklad ukazuje, jak vytvořit strom výrazu, který vypočítá faktoriál čísla.

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

Další informace naleznete v tématu Generování dynamických metod pomocí stromů výrazů v sadě Visual Studio 2010, které platí také pro novější verze sady Visual Studio.