Kifejezésfák létrehozása

A C#-fordító létrehozta az eddig látott összes kifejezésfát. Létrehozott egy lambda kifejezést, amely egy változóhoz van hozzárendelve, amely egy vagy hasonló típusként van begépelve Expression<Func<T>> . Sok esetben futásidőben állít össze egy kifejezést a memóriában.

A kifejezésfák nem módosíthatók. A megváltoztathatatlanság azt jelenti, hogy a fát a levelektől a gyökérig kell felépíteni. A kifejezésfák létrehozásához használt API-k ezt a tényt tükrözik: A csomópontok létrehozásához használt módszerek argumentumként veszik fel az összes gyermekét. Tekintsünk át néhány példát a technikák bemutatásához.

Csomópontok létrehozása

Az alábbi szakaszokban a már dolgozott összeadási kifejezéssel kell kezdenie:

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

A kifejezésfa létrehozásához először a levélcsomópontokat kell létrehoznia. A levélcsomópontok állandók. A csomópontok létrehozásához használja a Constant következő módszert:

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

Ezután hozza létre az összeadási kifejezést:

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

Miután létrehozta az összeadási kifejezést, létrehozza a lambda kifejezést:

var lambda = Expression.Lambda(addition);

Ez a lambda kifejezés nem tartalmaz argumentumokat. A szakasz későbbi részében megtudhatja, hogyan képezhet le argumentumokat paraméterekre, és hogyan hozhat létre bonyolultabb kifejezéseket.

Az ehhez hasonló kifejezések esetében az összes hívást egyetlen utasításba egyesítheti:

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

Fa létrehozása

Az előző szakasz a kifejezésfa memóriabeli létrehozásának alapjait mutatta be. Az összetettebb fák általában több csomóponttípust és több csomópontot jelentenek a fán. Futtassunk át még egy példát, és mutassunk be két további csomóponttípust, amelyeket általában a kifejezésfák létrehozásakor hoz létre: az argumentumcsomópontokat és a metódushívási csomópontokat. Hozzunk létre egy kifejezésfát a kifejezés létrehozásához:

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

Első lépésként hozzon létre paraméterkifejezéseket a következőhözx:y

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

A szorzási és összeadási kifejezések létrehozása a már látott mintát követi:

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

Ezután létre kell hoznia egy metódushívási kifejezést a híváshoz Math.Sqrt.

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

A GetMethod hívás vissza tud térni null , ha a metódus nem található. Valószínűleg azért, mert elírta a metódus nevét. Ellenkező esetben ez azt jelentheti, hogy a szükséges szerelvény nincs betöltve. Végül a metódushívást egy lambda kifejezésbe helyezi, és meg kell határoznia a lambda kifejezés argumentumait:

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

Ebben a bonyolultabb példában néhány további technikát láthat, amelyeket gyakran kell kifejezésfákat létrehoznia.

Először létre kell hoznia azokat az objektumokat, amelyek paramétereket vagy helyi változókat jelölnek a használatuk előtt. Miután létrehozta ezeket az objektumokat, bárhol használhatja őket a kifejezésfában.

Másodszor, a Önkifejezés ion API-k egy részhalmazával kell létrehoznia egy System.Reflection.MethodInfo objektumot, hogy létrehozhasson egy kifejezésfát a metódus eléréséhez. A .NET Core platformon elérhető Önkifejezés ion API-k részhalmazára kell korlátoznia magát. Ezek a technikák a többi kifejezésfára is kiterjednek.

Kód részletes összeállítása

Nem korlátozott, hogy mit hozhat létre ezekkel az API-kkal. Minél bonyolultabb kifejezésfát szeretne létrehozni, annál nehezebb a kód kezelése és olvasása.

Hozzunk létre egy kifejezésfát, amely megfelel a kódnak:

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

Az előző kód nem a kifejezésfát, hanem egyszerűen a meghatalmazottat építette ki. Az Expression osztály használatával nem hozhat létre utasítás lambdákat. Az alábbi kód szükséges ugyanahhoz a funkcióhoz. Nincs api a while hurkok létrehozásához, ehelyett létre kell készítenie egy olyan hurkot, amely feltételes tesztet és egy címkecélt tartalmaz a hurokból való kibontáshoz.

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

A faktoriális függvény kifejezésfáját összeállító kód meglehetősen hosszabb, bonyolultabb, és címkékkel, törési utasításokkal és egyéb elemekkel van tele, amelyeket el szeretne kerülni a mindennapi kódolási feladatok során.

Ebben a szakaszban kódot írt a kifejezésfa minden csomópontjának megtekintéséhez, és a mintában létrehozott csomópontokra vonatkozó információk kiírásához. A mintakódot a dotnet/docs GitHub-adattárban tekintheti meg vagy töltheti le. Kísérletezzen saját maga számára a minták létrehozásával és futtatásával.

Kódszerkezetek leképezése kifejezésekre

Az alábbi példakód egy olyan kifejezésfát mutat be, amely a lambda kifejezést num => num < 5 jelöli az API használatával.

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

A kifejezésfa API emellett támogatja a hozzárendeléseket és a folyamatkifejezések vezérlését, például hurkokat, feltételes blokkokat és try-catch blokkokat. Az API használatával olyan kifejezésfákat hozhat létre, amelyek összetettebbek, mint azok, amelyeket a C#-fordító Lambda-kifejezésekből hozhat létre. Az alábbi példa bemutatja, hogyan hozhat létre olyan kifejezésfát, amely kiszámítja egy szám faktoriálisát.

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

További információ: Dinamikus metódusok létrehozása kifejezésfákkal a Visual Studio 2010-ben, amely a Visual Studio későbbi verzióira is vonatkozik.