Bagikan melalui


Membangun pohon ekspresi

Pengkompilasi C# membuat semua pohon ekspresi yang telah Anda lihat sejauh ini. Anda membuat ekspresi lambda yang ditetapkan ke variabel yang diketik sebagai sebuah Expression<Func<T>> atau tipe serupa. Untuk banyak skenario, Anda membangun ekspresi dalam memori pada waktu proses.

Pohon ekspresi tidak dapat diubah. Menjadi tidak dapat diubah berarti Anda harus membangun pohon dari daun hingga akar. API yang Anda gunakan untuk membangun pohon ekspresi mencerminkan fakta ini: Metode yang Anda gunakan untuk membangun simpul mengambil semua turunannya sebagai argumen. Mari kita telusuri beberapa contoh untuk menunjukkan tekniknya kepada Anda.

Membuat simpul

Anda mulai dengan rumus penambahan yang telah Anda kerjakan di seluruh bagian ini:

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

Untuk membangun pohon ekspresi itu, Anda terlebih dahulu membuat simpul daun. Simpul daun adalah konstanta. Constant Gunakan metode untuk membuat simpul:

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

Selanjutnya, buat ekspresi penambahan:

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

Setelah membuat ekspresi penambahan, Anda membuat ekspresi lambda:

var lambda = Expression.Lambda(addition);

Ekspresi lambda ini tidak berisi argumen. Nantinya di bagian ini, Anda akan melihat cara memetakan argumen ke parameter dan membangun ekspresi yang lebih rumit.

Untuk ekspresi seperti ini, Anda dapat menggabungkan semua panggilan ke dalam satu pernyataan:

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

Membangun pohon

Bagian sebelumnya memperlihatkan dasar-dasar membangun pohon ekspresi dalam memori. Pohon yang lebih kompleks umumnya berarti lebih banyak jenis node, dan lebih banyak simpul di pohon. Mari kita jalankan melalui satu contoh lagi dan menunjukkan dua jenis node lagi yang biasanya Anda buat saat membuat pohon ekspresi: simpul argumen, dan node panggilan metode. Mari kita buat pohon ekspresi untuk membuat ekspresi ini:

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

Anda mulai dengan membuat ekspresi parameter untuk x dan y:

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

Membuat ekspresi perkalian dan penambahan mengikuti pola yang telah Anda lihat:

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

Selanjutnya, Anda perlu membuat ekspresi panggilan metode untuk panggilan ke Math.Sqrt.

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

Panggilan GetMethod dapat mengembalikan null jika fungsi tidak ditemukan. Kemungkinan besar itu karena Anda salah eja nama metode. Jika tidak, itu bisa berarti komponen yang diperlukan tidak tersedia. Terakhir, Anda memasukkan pemanggilan metode ke dalam ekspresi lambda, dan pastikan Anda mendefinisikan argumen ekspresi lambda:

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

Dalam contoh yang lebih rumit ini, Anda melihat beberapa teknik lagi yang sering Anda butuhkan untuk membuat pohon ekspresi.

Pertama, Anda perlu membuat objek yang mewakili parameter atau variabel lokal sebelum Anda menggunakannya. Setelah membuat objek tersebut, Anda dapat menggunakannya di pohon ekspresi di mana pun Anda membutuhkannya.

Kedua, Anda perlu menggunakan subset API Pantulan untuk membuat System.Reflection.MethodInfo objek sehingga Anda dapat membuat pohon ekspresi untuk mengakses metode tersebut. Anda harus membatasi diri Anda pada subset API Refleksi yang tersedia di platform .NET Core. Sekali lagi, teknik ini meluas ke pohon ekspresi lainnya.

Membangun kode secara mendalam

Anda tidak terbatas pada apa yang dapat Anda buat menggunakan API ini. Namun, semakin rumit pohon ekspresi yang ingin Anda bangun, semakin sulit kodenya adalah mengelola dan membaca.

Mari kita buat pohon ekspresi yang setara dengan kode ini:

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

Kode sebelumnya tidak membangun pohon ekspresi, tetapi hanya delegate. Dengan menggunakan kelas Expression, Anda tidak dapat membuat lambda statement. Berikut adalah kode yang diperlukan untuk membangun fungsionalitas yang sama. Tidak ada API untuk membangun perulangan while. Sebagai gantinya, Anda perlu membangun perulangan yang berisi pengujian kondisi dan sasaran label untuk menghentikan perulangan.

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

Kode untuk membangun pohon ekspresi fungsi faktorial cukup panjang dan rumit, serta dipenuhi dengan label, pernyataan jeda, dan elemen lain yang ingin Anda hindari dalam tugas pengodean sehari-hari Anda.

Untuk bagian ini, Anda menulis kode untuk mengunjungi setiap simpul di pohon ekspresi ini dan menuliskan informasi tentang simpul yang dibuat dalam sampel ini. Anda dapat melihat atau mengunduh kode sampel di repositori GitHub dotnet/docs. Bereksperimenlah sendiri dengan membangun dan menjalankan sampel.

Memetakan konstruksi kode ke ekspresi

Contoh kode berikut menunjukkan pohon ekspresi yang mewakili ekspresi num => num < 5 lambda dengan menggunakan 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 });

API pohon ekspresi juga mendukung penugasan dan ekspresi kontrol aliran seperti perulangan, blok kondisional, dan try-catch blok. Dengan menggunakan API, Anda dapat membuat pohon ekspresi yang lebih kompleks daripada yang dapat dibuat dari ekspresi lambda oleh pengkompilasi C#. Contoh berikut menunjukkan cara membuat pohon ekspresi yang menghitung faktorial angka.

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

Untuk informasi selengkapnya, lihat Membuat Metode Dinamis dengan Pohon Ekspresi di Visual Studio 2010, yang juga berlaku untuk versi Visual Studio yang lebih baru.