Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Kompilátor jazyka C# vytvořil všechny stromy výrazů, které jste zatím viděli. Vytvořili jste lambda výraz přiřazený proměnné typu Expression<Func<T>> nebo nějakého podobného typu. V mnoha scénářích za běhu sestavujete výraz v paměti.
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ů, toto zohledňují: Metody, které používáte ke stavbě uzlu, přebírají všechny jeho potomky 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;
Aby byl tento strom výrazů vytvořen, nejprve vytvoříte listové uzly. Listové uzly 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 sčítací vyjádření:
var addition = Expression.Add(one, two);
Jakmile vytvoříte výraz 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 pro volání metody Math.Sqrt.
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, abyste vytvořili 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.
Za druhé, musíte použít podmnožinu rozhraní Reflection API k vytvoření objektu System.Reflection.MethodInfo , abyste mohli vytvořit strom výrazů pro přístup k této metodě. Musíte se omezit na podmnožinu reflexních rozhraní API, která jsou k dispozici na platformě .NET Core. Tyto techniky se opět rozšiřují na další stromy výrazů.
Podrobné sestavení kódu
Nejste omezeni tím, co můžete vytvářet pomocí těchto rozhraní API. Čím složitější strom výrazů chcete sestavit, tím obtížněji se kód spravuje a čte.
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. Pomocí Expression třídy nemůžete sestavovat příkazové 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ínkový test a cíl označení, na kterém je možné smyčku přerušit.
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 delší a složitější a je plný označení, příkazů break a dalších prvků, kterým byste se chtěli vyhnout v každodenní praxi programová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. Vyzkoušejte si sami sestavením a spuštěním ukázek.
Mapování konstruktů 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 stromů výrazů také podporuje přiřazení a výrazy pro tok ří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.