Lambda pada bab-bab sebelumnya memakai variabel yang bukan parameternya, seperti minimalGratisOngkir. Bagaimana sebuah fungsi kecil dapat mengingat variabel di luar dirinya? Jawabannya adalah closure: compiler membungkus variabel yang ditangkap ke dalam satu objek, dan lambda mengaksesnya lewat objek tersebut, bukan lewat salinan nilainya.

Istilah “menutup” (closing over) merujuk pada lambda yang menutup dirinya di atas variabel luar. Implementasinya mekanis: compiler membuat satu class pembungkus yang menampung variabel yang ditangkap, lalu seluruh lambda yang menutup atas variabel yang sama berbagi objek pembungkus itu. Itulah sebabnya dua lambda yang menangkap variabel yang sama saling melihat perubahan satu sama lain. Konsep ini penting dipahami sebelum masuk ke contoh, karena kedua perilaku di bawah berangkat dari aturan yang sama.

Menangkap Variabel, Bukan Nilai #

csharp
int kupon = 10000;
Func<int, int> hitungBayar = total => total - kupon;

Console.WriteLine(hitungBayar(50000));

kupon = 25000;
Console.WriteLine(hitungBayar(50000));

Func<int> penghitung = BuatPenghitung();
penghitung();
penghitung();
Console.WriteLine(penghitung());

static Func<int> BuatPenghitung()
{
    int hitungan = 0;
    return () => ++hitungan;
}

Output yang Dihasilkan:

text
40000
25000
3

Bedah Kode Baris per Baris #

Mari kita telaah kode di atas:

  1. Baris 3: Lambda hitungBayar menangkap variabel kupon. Yang ditangkap adalah variabelnya, bukan nilai 10000 saat itu.
  2. Baris 5-8: Perubahan kupon menjadi 25000 langsung terasa pada pemanggilan berikutnya: hasil turun dari 40000 menjadi 25000. Bukti ini menegaskan semantik by reference.
  3. Baris 10-13: BuatPenghitung mendeklarasikan hitungan lalu mengembalikan lambda yang menaikannya. Setelah method selesai, variabel hitungan tetap hidup karena lambda memegangnya.
  4. Baris 11-13: Tiga pemanggilan penghitung() menghasilkan 1, 2, dan 3; dua panggilan pertama tanpa pencetakan, dan panggilan ketiga mencetak 3.

Contoh penghitung adalah pola stateful yang sah: closure memberi lambda memori antar panggilan tanpa perlu field class. Namun kebiasaan yang sama menjadi sumber bug bila variabel yang ditangkap berubah tanpa sengaja; bab Variable Capture menunjukkan kasus loop-nya.