İfade ağaçları oluşturma

C# derleyicisi, şimdiye kadar gördüğünüz tüm ifade ağaçlarını oluşturmuştur. Veya benzer bir tür olarak yazılan bir değişkene atanmış bir Expression<Func<T>> lambda ifadesi oluşturdunuz. Birçok senaryoda, çalışma zamanında bellekte bir ifade oluşturursunuz.

İfade ağaçları sabittir. Sabit olmak, ağacı yapraklardan köke kadar oluşturmanız gerektiği anlamına gelir. İfade ağaçları oluşturmak için kullandığınız API'ler şu gerçeği yansıtır: Düğüm oluşturmak için kullandığınız yöntemler, tüm alt öğelerini bağımsız değişken olarak alır. Şimdi size teknikleri göstermek için birkaç örnek inceleyelim.

Düğüm oluşturma

Şu bölümler boyunca üzerinde çalıştığınız ekleme ifadesiyle başlarsınız:

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

Bu ifade ağacını oluşturmak için önce yaprak düğümleri oluşturursunuz. Yaprak düğümler sabittir. Düğümleri Constant oluşturmak için yöntemini kullanın:

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

Ardından toplama ifadesini derleyin:

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

Ekleme ifadesini oluşturduktan sonra lambda ifadesini oluşturursunuz:

var lambda = Expression.Lambda(addition);

Bu lambda ifadesi bağımsız değişken içermiyor. Bu bölümün ilerleyen bölümlerinde bağımsız değişkenleri parametrelerle eşlemeyi ve daha karmaşık ifadeler oluşturmayı göreceksiniz.

Bunun gibi ifadeler için tüm çağrıları tek bir deyimde birleştirebilirsiniz:

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

Ağaç oluşturma

Önceki bölümde, bellekte bir ifade ağacı oluşturmanın temelleri gösterildi. Daha karmaşık ağaçlar genellikle daha fazla düğüm türü ve ağaçta daha fazla düğüm anlamına gelir. Şimdi bir örnek daha çalıştıralım ve ifade ağaçlarını oluştururken genellikle oluşturduğunuz iki düğüm türünü daha gösterelim: bağımsız değişken düğümleri ve yöntem çağrı düğümleri. Şimdi şu ifadeyi oluşturmak için bir ifade ağacı oluşturalım:

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

başlangıç olarak ve yiçin x parametre ifadeleri oluşturursunuz:

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

Çarpma ve ekleme ifadelerini oluşturmak, daha önce gördüğünüz desene uyar:

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

Ardından, çağrısı için bir yöntem çağrı Math.Sqrtifadesi oluşturmanız gerekir.

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

GetMethod Yöntemi bulunamazsa çağrı döndürülebilirnull. Bunun nedeni büyük olasılıkla yöntem adını yanlış yazmış olmanızdır. Aksi takdirde, gerekli derlemenin yüklenmemiş olduğu anlamına gelebilir. Son olarak, yöntem çağrısını bir lambda ifadesine koyarsınız ve lambda ifadesinde bağımsız değişkenleri tanımladığınızdan emin olursunuz:

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

Bu daha karmaşık örnekte, ifade ağaçları oluşturmak için sık sık ihtiyaç duyduğunuz birkaç teknik daha görürsünüz.

İlk olarak, parametreleri veya yerel değişkenleri kullanmadan önce temsil eden nesneleri oluşturmanız gerekir. Bu nesneleri oluşturduktan sonra, bunları istediğiniz yerde ifade ağacınızda kullanabilirsiniz.

İkincisi, bir nesne oluşturmak için Düşünceler ion API'lerinin bir System.Reflection.MethodInfo alt kümesini kullanmanız gerekir, böylece bu yönteme erişmek için bir ifade ağacı oluşturabilirsiniz. Kendinizi .NET Core platformunda bulunan Düşünceler ion API'lerinin alt kümesiyle sınırlamanız gerekir. Bu teknikler yine diğer ifade ağaçlarını da genişletir.

Ayrıntılı kod oluşturma

Bu API'leri kullanarak oluşturabileceklerinizde sınırlı değilsiniz. Ancak, oluşturmak istediğiniz karmaşık ifade ağacı ne kadar karmaşık olursa, kodu yönetmek ve okumak o kadar zor olur.

Şimdi bu kodun eşdeğeri olan bir ifade ağacı oluşturalım:

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

Yukarıdaki kod ifade ağacını oluşturmadı, yalnızca temsilciyi oluşturuyordu. sınıfını Expression kullanarak lambdas deyimi oluşturamazsınız. Aynı işlevselliği oluşturmak için gereken kod aşağıdadır. Döngü oluşturmaya yönelik bir while API yoktur, bunun yerine koşullu test içeren bir döngü ve döngüden çıkmak için bir etiket hedefi oluşturmanız gerekir.

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

Faktöriyel işlev için ifade ağacını oluşturmaya yönelik kod biraz daha uzun, daha karmaşıktır ve günlük kodlama görevlerimizde kaçınmak istediğiniz etiketler ve kesme deyimleri ile diğer öğelerle dolu.

Bu bölümde, bu ifade ağacındaki her düğümü ziyaret etmek için kod yazdınız ve bu örnekte oluşturulan düğümlerle ilgili bilgileri yazdınız. Örnek kodu dotnet/docs GitHub deposunda görüntüleyebilir veya indirebilirsiniz. Örnekleri oluşturup çalıştırarak kendiniz için denemeler yapın.

Kod yapılarını ifadelerle eşleme

Aşağıdaki kod örneği, API kullanarak lambda ifadesini temsil eden bir ifade num => num < 5 ağacını gösterir.

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

İfade ağaçları API'si ayrıca atamaları destekler ve döngüler, koşullu bloklar ve try-catch bloklar gibi akış ifadelerini denetler. API'yi kullanarak, C# derleyicisi tarafından lambda ifadelerinden oluşturulabilenlerden daha karmaşık ifade ağaçları oluşturabilirsiniz. Aşağıdaki örnekte, bir sayının faktöriyelini hesaplayan bir ifade ağacının nasıl oluşturulacağı gösterilmektedir.

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

Daha fazla bilgi için bkz . Visual Studio'nun sonraki sürümleri için de geçerli olan Visual Studio 2010'da İfade Ağaçları ile Dinamik Yöntemler Oluşturma.