Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Pohon ekspresi adalah struktur data yang mewakili beberapa kode. Kode ini tidak dikompilasi dan dapat dieksekusi. Jika Anda ingin menjalankan kode .NET yang diwakili oleh pohon ekspresi, Anda harus mengonversinya menjadi instruksi IL yang dapat dieksekusi. Menjalankan pohon ekspresi dapat mengembalikan nilai, atau mungkin hanya melakukan tindakan seperti memanggil metode.
Hanya pohon ekspresi yang mewakili ekspresi lambda yang dapat dijalankan. Pohon ekspresi yang mewakili ekspresi lambda berjenis LambdaExpression atau Expression<TDelegate>. Untuk menjalankan pohon ekspresi ini, panggil metode Compile untuk membuat delegasi yang dapat dieksekusi, lalu jalankan delegasi tersebut.
Nota
Jika jenis delegasi tidak diketahui, artinya, ekspresi lambda berjenis LambdaExpression dan bukan Expression<TDelegate>, panggil DynamicInvoke metode pada delegasi alih-alih memanggilnya secara langsung.
Jika pohon ekspresi tidak mewakili ekspresi lambda, Anda dapat membuat ekspresi lambda baru yang memiliki pohon ekspresi asli sebagai isinya, dengan memanggil Lambda<TDelegate>(Expression, IEnumerable<ParameterExpression>) metode . Kemudian, Anda dapat menjalankan ekspresi lambda seperti yang dijelaskan sebelumnya di bagian ini.
Menjadikan ekspresi lambda menjadi fungsi
Anda dapat mengonversi LambdaExpression apa pun, atau jenis apa pun yang berasal dari LambdaExpression menjadi IL yang dapat dieksekusi. Jenis ekspresi lain tidak dapat dikonversi secara langsung menjadi kode. Pembatasan ini memiliki sedikit efek dalam praktiknya. Ekspresi Lambda adalah satu-satunya jenis ekspresi yang ingin Anda jalankan dengan mengonversi ke bahasa perantara (IL) yang dapat dieksekusi. (Pikirkan tentang apa artinya langsung mengeksekusi System.Linq.Expressions.ConstantExpression. Apakah itu berarti sesuatu yang berguna?) Setiap pohon ekspresi yang merupakan System.Linq.Expressions.LambdaExpression, atau jenis yang berasal dari LambdaExpression dapat dikonversi ke IL. Jenis System.Linq.Expressions.Expression<TDelegate> ekspresi adalah satu-satunya contoh konkret dalam pustaka .NET Core. Ini digunakan untuk mewakili ekspresi yang sesuai dengan tipe delegasi apa pun. Karena jenis ini berkorespondensi dengan jenis delegasi, .NET dapat memeriksa ekspresi dan menghasilkan konversi IL untuk delegasi yang sesuai dengan tanda tangan ekspresi lambda. Jenis delegasi didasarkan pada jenis ekspresi. Anda harus mengetahui jenis pengembalian dan daftar argumen jika Anda ingin menggunakan objek delegasi dengan cara yang sangat ditik. Metode LambdaExpression.Compile() mengembalikan jenis Delegate. Anda harus mengubahnya ke tipe delegasi yang tepat agar alat saat kompilasi dapat memeriksa daftar argumen atau tipe pengembalian.
Dalam kebanyakan kasus, ada pemetaan sederhana antara ekspresi dan delegasi yang sesuai. Misalnya, pohon ekspresi yang diwakili oleh Expression<Func<int>> akan dikonversi ke delegasi jenis Func<int>. Untuk ekspresi lambda dengan jenis pengembalian dan daftar argumen apa pun, ada jenis delegasi yang merupakan jenis target untuk kode yang dapat dieksekusi yang diwakili oleh ekspresi lambda tersebut.
Jenis System.Linq.Expressions.LambdaExpression berisi LambdaExpression.Compile anggota dan LambdaExpression.CompileToMethod yang akan Anda gunakan untuk mengonversi pohon ekspresi ke kode yang dapat dieksekusi. Metode Compile membuat deleget. Metode CompileToMethod memperbarui objek System.Reflection.Emit.MethodBuilder dengan IL yang merepresentasikan output yang dikompilasi dari pohon ekspresi.
Penting
CompileToMethod hanya tersedia di .NET Framework, bukan di .NET Core atau .NET 5 dan yang lebih baru.
Secara opsional, Anda juga dapat menyediakan System.Runtime.CompilerServices.DebugInfoGenerator yang menerima informasi debugging simbol untuk objek delegasi yang dihasilkan.
DebugInfoGenerator menyediakan informasi debug lengkap tentang delegasi yang dihasilkan.
Anda akan mengonversi ekspresi menjadi delegate menggunakan kode berikut:
Expression<Func<int>> add = () => 1 + 2;
var func = add.Compile(); // Create Delegate
var answer = func(); // Invoke Delegate
Console.WriteLine(answer);
Contoh kode berikut menunjukkan jenis beton yang digunakan saat Anda mengkompilasi dan menjalankan pohon ekspresi.
Expression<Func<int, bool>> expr = num => num < 5;
// Compiling the expression tree into a delegate.
Func<int, bool> result = expr.Compile();
// Invoking the delegate and writing the result to the console.
Console.WriteLine(result(4));
// Prints True.
// You can also use simplified syntax
// to compile and run an expression tree.
// The following line can replace two previous statements.
Console.WriteLine(expr.Compile()(4));
// Also prints True.
Contoh kode berikut menunjukkan cara menjalankan pohon ekspresi yang mewakili menaikkan angka ke kekuatan dengan membuat ekspresi lambda dan mengeksekusinya. Hasilnya, yang menunjukkan angka yang dinaikkan ke daya, ditampilkan.
// The expression tree to execute.
BinaryExpression be = Expression.Power(Expression.Constant(2d), Expression.Constant(3d));
// Create a lambda expression.
Expression<Func<double>> le = Expression.Lambda<Func<double>>(be);
// Compile the lambda expression.
Func<double> compiledExpression = le.Compile();
// Execute the lambda expression.
double result = compiledExpression();
// Display the result.
Console.WriteLine(result);
// This code produces the following output:
// 8
Eksekusi dan masa pakai
Anda menjalankan kode dengan memanggil delegasi yang dibuat saat Anda memanggil LambdaExpression.Compile(). Kode sebelumnya, add.Compile(), mengembalikan delegasi. Anda memanggil delegasi tersebut dengan memanggil func(), yang menjalankan kode.
Delegasi tersebut mewakili kode di pohon ekspresi. Anda dapat menyimpan referensi ke delegat tersebut dan mengaktifkannya nanti. Anda tidak perlu mengkompilasi pohon ekspresi setiap kali Anda ingin menjalankan kode yang diwakilinya. (Ingatlah bahwa pohon ekspresi tidak dapat diubah, dan mengkompilasi pohon ekspresi yang sama kemudian membuat delegasi yang menjalankan kode yang sama.)
Perhatian
Jangan membuat mekanisme cache yang lebih canggih untuk mempercepat kinerja dengan menghindari panggilan kompilasi yang tidak perlu. Membandingkan dua pohon ekspresi sewenang-wenang untuk menentukan apakah pohon ekspresi mewakili algoritma yang sama adalah operasi yang memakan waktu. Waktu komputasi yang Anda hemat dengan menghindari panggilan tambahan ke LambdaExpression.Compile() kemungkinan lebih sedikit dibandingkan dengan waktu yang digunakan oleh kode yang mengeksekusi untuk menentukan apakah dua pohon ekspresi yang berbeda menghasilkan kode yang dapat dieksekusi yang identik.
Catatan Penting
Mengkompilasi ekspresi lambda ke delegasi dan memanggil delegasi tersebut adalah salah satu operasi paling sederhana yang dapat Anda lakukan dengan pohon ekspresi. Namun, bahkan dengan operasi sederhana ini, ada peringatan yang harus Anda waspadai.
Ekspresi Lambda membuat penutupan atas variabel lokal apa pun yang direferensikan dalam ekspresi. Anda harus menjamin bahwa variabel apa pun yang akan menjadi bagian dari delegasi dapat digunakan di lokasi tempat Anda memanggil Compile, dan ketika Anda menjalankan delegasi yang dihasilkan. Pengkompilasi memastikan bahwa variabel berada dalam cakupan. Namun, jika ekspresi Anda mengakses variabel yang mengimplementasikan IDisposable, ada kemungkinan kode Anda dapat membuang objek saat masih dipegang oleh pohon ekspresi.
Misalnya, kode ini berfungsi dengan baik, karena int tidak menerapkan IDisposable:
private static Func<int, int> CreateBoundFunc()
{
var constant = 5; // constant is captured by the expression tree
Expression<Func<int, int>> expression = (b) => constant + b;
var rVal = expression.Compile();
return rVal;
}
Delegasi telah mengambil referensi ke variabel lokal constant. Variabel tersebut diakses kapan saja nanti, ketika fungsi yang dikembalikan oleh CreateBoundFunc dijalankan.
Namun, pertimbangkan kelas berikut (agak dipaksakan) yang mengimplementasikan System.IDisposable:
public class Resource : IDisposable
{
private bool _isDisposed = false;
public int Argument
{
get
{
if (!_isDisposed)
return 5;
else throw new ObjectDisposedException("Resource");
}
}
public void Dispose()
{
_isDisposed = true;
}
}
Jika Anda menggunakannya dalam ekspresi seperti yang ditunjukkan dalam kode berikut, Anda mendapatkan System.ObjectDisposedException saat menjalankan kode yang direferensikan Resource.Argument oleh properti :
private static Func<int, int> CreateBoundResource()
{
using (var constant = new Resource()) // constant is captured by the expression tree
{
Expression<Func<int, int>> expression = (b) => constant.Argument + b;
var rVal = expression.Compile();
return rVal;
}
}
Delegasi yang dikembalikan dari metode ini telah menutup objek constant , yang telah dibuang. (Sudah dibuang, karena dinyatakan dalam pernyataan using .)
Sekarang, ketika Anda menjalankan delegat yang dikembalikan dari metode ini, Anda akan mengalami ObjectDisposedException dilemparkan pada titik eksekusi.
Tampaknya aneh memiliki kesalahan waktu proses yang mewakili konstruksi siklus kompilasi, tetapi itulah dunia yang Anda hadapi ketika Anda bekerja dengan struktur ekspresi.
Ada banyak permutasi dari masalah ini, jadi sulit untuk menawarkan panduan umum untuk menghindarinya. Berhati-hatilah dalam mengakses variabel lokal saat menentukan ekspresi, dan berhati-hatilah dalam mengakses status di objek saat ini (diwakili oleh this) saat membuat pohon ekspresi yang dikembalikan melalui API publik.
Kode dalam ekspresi Anda dapat mereferensikan metode atau properti di rakitan lain. Assembly tersebut harus dapat diakses ketika ekspresi didefinisikan, ketika dikompilasi, dan ketika delegate yang dihasilkan dipanggil. Anda bertemu dengan ReferencedAssemblyNotFoundException dalam kasus di mana itu tidak ada.
Ringkasan
Pohon Ekspresi yang mewakili ekspresi lambda dapat dikompilasi untuk menciptakan delegasi yang dapat Anda eksekusi. Pohon ekspresi menyediakan satu mekanisme untuk menjalankan kode yang diwakili oleh pohon ekspresi.
Pohon Ekspresi mewakili kode yang akan dijalankan untuk konstruksi tertentu yang Anda buat. Selama lingkungan tempat Anda mengkompilasi dan menjalankan kode cocok dengan lingkungan tempat Anda membuat ekspresi, semuanya berfungsi seperti yang diharapkan. Ketika itu tidak terjadi, kesalahan dapat diprediksi dan mudah ditemukan pada pengujian pertama terhadap kode apa pun yang menggunakan tree ekspresi.