Selama ini lambda kita eksekusi. Ada saat lain ketika kita justru ingin membaca lambda: kerangka ORM menerjemahkan kriteria harga > 50000 menjadi SQL, dan library validasi memeriksa nama properti yang disebut lambda. Untuk kebutuhan itu, C# menyediakan Expression<TDelegate>: tipe yang membuat compiler membangun lambda menjadi pohon node yang dapat kita telusuri, bukan menjadi kode yang langsung berjalan.

Perbedaannya hanya ada pada tipe target penugasan. Func<int, bool> menyuruh compiler menghasilkan IL, sedangkan Expression<Func<int, bool>> menyuruh compiler membangun struktur data pohon. Tipe pohon menyediakan properti Parameters dan Body untuk penelusuran, serta method Compile() untuk kembali menjadi delegate yang dapat dipanggil.

Lambda sebagai Pohon Node #

csharp
Expression<Func<int, bool>> kriteria = harga => harga > 50000;

Console.WriteLine($"Parameter : {kriteria.Parameters[0].Name}");
Console.WriteLine($"Body      : {kriteria.Body.NodeType}");

BinaryExpression perbandingan = (BinaryExpression)kriteria.Body;
Console.WriteLine($"Kiri      : {((ParameterExpression)perbandingan.Left).Name}");
Console.WriteLine($"Kanan     : {((ConstantExpression)perbandingan.Right).Value}");

Func<int, bool> delegat = kriteria.Compile();
Console.WriteLine($"Cek 75000 : {delegat(75000)}");
Console.WriteLine($"Cek 32000 : {delegat(32000)}");

Output yang Dihasilkan:

text
Parameter : harga
Body      : GreaterThan
Kiri      : harga
Kanan     : 50000
Cek 75000 : True
Cek 32000 : False

Struktur yang dibaca kode di atas dapat digambarkan sebagai pohon berikut:

text
Lambda  harga => harga > 50000
└─ BinaryExpression  NodeType: GreaterThan
   ├─ Kiri  : ParameterExpression "harga"
   └─ Kanan : ConstantExpression 50000

Bedah Kode Baris per Baris #

Mari kita telaah kode di atas:

  1. Baris 1: Menambahkan Expression<...> di depan tipe delegate membuat compiler menghasilkan pohon node, bukan method terkompilasi. Satu perubahan tipe ini saja yang membedakannya dari bab-bab sebelumnya.
  2. Baris 3-4: Koleksi Parameters menampung parameter lambda, dan Body.NodeType menyebut jenis operasi akar, yaitu GreaterThan.
  3. Baris 6-8: Body di-cast ke BinaryExpression karena operasi perbandingan selalu memiliki dua sisi: Left bertipe parameter dan Right bertipe konstanta.
  4. Baris 10-12: Compile() mengubah pohon kembali menjadi Func<int, bool> yang benar-benar dapat dipanggil, terbukti dari hasil True dan False di output.

Class ExpressionVisitor dapat menjelajahi pohon yang lebih dalam tanpa mengubah node-nya, dan provider LINQ memakai teknik tersebut untuk menerjemahkan kriteria ke bahasa lain. Sebagai fondasi, kemampuan membaca node pada contoh di atas sudah mencakup inti idenya.