Pemanggilan method biasa selesai sebelum baris berikutnya dieksekusi. Iterator method berbeda: pemanggilan MenuVegetarian() pada bab sebelumnya tidak menjalankan satu baris pun dari badan method, melainkan hanya mengembalikan objek urutan yang siap dibaca. Karakter ini disebut lazy iteration atau eksekusi tunda. Sifat ini menjelaskan perilaku yield return yang mungkin terasa aneh pada pertama kali dilihat, sekaligus menjadi fondasi eksekusi LINQ yang dibahas di section tersendiri.

Bagaimana Eksekusi Ditunda #

Compiler mengubah badan iterator method menjadi state machine yang dikendalikan MoveNext():

  • Saat method dipanggil, hanya state machine yang dibuat; tidak ada pernyataan di badan yang berjalan.
  • Setiap MoveNext() menjalankan badan hingga yield return berikutnya, lalu berhenti di sana.
  • Setelah item terakhir dikirim, sisa badan dieksekusi dan MoveNext() mengembalikan false.

Karena state machine baru dibuat untuk setiap pemanggilan GetEnumerator(), dua enumerasi atas variabel yang sama menjalankan badan method dua kali penuh.

Contoh Jejak Eksekusi #

Method berikut mencetak jejak [iterator] agar urutan eksekusi terlihat:

csharp
public static IEnumerable<string> SumberCatatan()
{
    Console.WriteLine("  [iterator] mulai menghasilkan item");
    yield return "Catatan A";
    Console.WriteLine("  [iterator] lanjut ke item berikutnya");
    yield return "Catatan B";
    Console.WriteLine("  [iterator] selesai");
}

IEnumerable<string> catatan = SumberCatatan();

Console.WriteLine("Method sudah dipanggil, iterator belum berjalan");
Console.WriteLine("Enumerasi pertama:");
foreach (string isi in catatan)
{
    Console.WriteLine(isi);
}
Console.WriteLine("Enumerasi kedua:");
foreach (string isi in catatan)
{
    Console.WriteLine(isi);
}

Output yang Dihasilkan:

text
Method sudah dipanggil, iterator belum berjalan
Enumerasi pertama:
  [iterator] mulai menghasilkan item
Catatan A
  [iterator] lanjut ke item berikutnya
Catatan B
  [iterator] selesai
Enumerasi kedua:
  [iterator] mulai menghasilkan item
Catatan A
  [iterator] lanjut ke item berikutnya
Catatan B
  [iterator] selesai

Mari kita bedah urutan keluarannya:

  1. Method sudah dipanggil, iterator belum berjalan: baris ini tercetak sebelum jejak [iterator] mana pun, membuktikan pemanggilan SumberCatatan() belum mengeksekusi badan method.
  2. Jejak pertama muncul setelah label Enumerasi pertama: pernyataan Console.WriteLine pertama di badan iterator baru berjalan ketika foreach memanggil MoveNext() untuk pertama kali.
  3. Selang-seling Catatan A dan jejak: setiap yield return menyerahkan kendali kembali ke konsumen; badan method bergerak satu potongan per MoveNext().
  4. Enumerasi kedua mengulang jejak yang sama: enumerasi kedua membuat state machine baru, sehingga seluruh badan method, termasuk jejak awal, berjalan ulang dari nol.

Konsekuensi desainnya nyata: iterator yang membaca sumber berubah, misalnya data stok terkini, menghasilkan data terbaru pada setiap enumerasi. Sifat ini menguntungkan, asalkan kita menyadarinya.