Deklarasi kueri LINQ tidak menjalankan apa pun. Kueri hanya menyimpan resep penyaringan, dan eksekusinya ditunda sampai hasilnya benar-benar diminta — perilaku ini disebut deferred execution. Bukti paling jelas datang dari mengubah sumber data setelah kueri dibuat.

Kueri Sebagai Resep #

Contoh berikut membuat kueri, menambahkan satu produk, lalu mengiterasi kueri yang sama dua kali:

csharp
record Produk(string Nama, string Kategori, int Harga, int Stok);

List<Produk> produk = new()
{
    new Produk("Kopi Gayo", "Minuman", 25000, 40),
    new Produk("Teh Melati", "Minuman", 15000, 60),
    new Produk("Susu UHT", "Minuman", 20000, 35)
};

var mahal = produk.Where(p => p.Harga > 15000);

Console.WriteLine("Sebelum tambah:");
foreach (var p in mahal)
{
    Console.WriteLine(p.Nama);
}

produk.Add(new Produk("Kopi Toraja", "Minuman", 30000, 10));

Console.WriteLine("Setelah tambah:");
foreach (var p in mahal)
{
    Console.WriteLine(p.Nama);
}

Output yang Dihasilkan:

text
Sebelum tambah:
Kopi Gayo
Susu UHT
Setelah tambah:
Kopi Gayo
Susu UHT
Kopi Toraja

Mari kita bedah baris demi baris:

  1. var mahal = produk.Where(...): baris ini hanya membungkus sumber dan predikat menjadi satu object kueri; belum ada elemen yang diperiksa.
  2. foreach pertama: iterasi memicu eksekusi kueri; dua produk lolos syarat harga.
  3. produk.Add(...): produk baru ditambahkan setelah kueri dibuat, tepat di antara dua iterasi.
  4. foreach kedua: kueri dieksekusi ulang atas kondisi sumber saat ini, sehingga Kopi Toraja ikut muncul.
  5. kesimpulan: hasil kueri mengikuti keadaan sumber pada saat iterasi, bukan pada saat deklarasi.

Mekanisme di Balik Layar #

Mekanisme di balik perilaku ini adalah iterator: Where mengembalikan object yang menarik elemen dari sumber satu per satu hanya ketika foreach memintanya. Kueri tidak memegang salinan data; object kueri hanya menyimpan alamat sumber beserta predikat yang akan diterapkan. Konsekuensinya, satu kueri yang diiterasi dua kali menjalankan penyaringan dua kali; bab LINQ Performance mengukur dampaknya.

Sifat ini juga membuat kueri dapat disusun dari potongan kecil: variabel mahal di atas bisa menjadi sumber bagi kueri lain, misalnya mahal.OrderBy(p => p.Nama), tanpa mengeksekusi kueri pendahulunya lebih dulu. Seluruh rantai baru berjalan ketika hasil akhirnya diiterasi.

Operator yang justru langsung mengeksekusi kueri saat dipanggil dibahas pada bab Immediate Execution.