Pernyataan foreach sudah kita pakai sejak section Dasar Pemrograman, tetapi foreach bukanlah fitur bawaan koleksi. foreach hanyalah singkatan yang ditulis compiler atas kontrak iterasi milik IEnumerable<T> dan IEnumerator<T>. Memahami kedua interface ini penting karena seluruh ekosistem .NET, mulai dari LINQ sampai serialisasi JSON, bergerak di atas kontrak yang sama.

Dua Interface yang Saling Melengkapi #

IEnumerable<T> mewakili “sesuatu yang bisa diiterasi” dan hanya punya satu member, yaitu method GetEnumerator() yang mengembalikan enumerator. IEnumerator<T> mewakili “satu sesi pembacaan” dengan tiga member utama:

  • MoveNext(): maju ke item berikutnya, mengembalikan false saat item habis.
  • Current: membaca item pada posisi sekarang.
  • Dispose(): membersihkan sumber daya enumerator ketika iterasi selesai.

Satu objek bisa diiterasi berkali-kali karena setiap pemanggilan GetEnumerator() membuat enumerator baru, sementara satu enumerator hanya menyeberangi koleksi satu kali.

Iterasi Manual Tanpa foreach #

Contoh berikut melakukan persis pekerjaan yang biasanya compiler lakukan untuk foreach:

csharp
List<string> pesertaLomba = new List<string> { "Sari", "Dimas", "Lina" };

IEnumerable<string> sumberPeserta = pesertaLomba;
IEnumerator<string> enumerator = sumberPeserta.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        Console.WriteLine(enumerator.Current);
    }
}
finally
{
    enumerator.Dispose();
}

Output yang Dihasilkan:

text
Sari
Dimas
Lina

Mari kita bedah baris demi baris:

  1. IEnumerable<string> sumberPeserta = pesertaLomba;: List<T> dipandang lewat kontrak paling longgar; kode setelah baris ini tidak tahu objek aslinya adalah List<T>.
  2. sumberPeserta.GetEnumerator(): sesi pembacaan baru diminta; enumerator memulai posisi sebelum item pertama.
  3. while (enumerator.MoveNext()): setiap putaran maju satu item; method mengembalikan true selama masih ada item, dan false ketika ketiga nama selesai dibaca.
  4. enumerator.Current: nama pada posisi sekarang dibaca dan dicetak; member ini hanya valid setelah MoveNext() mengembalikan true.
  5. finally { enumerator.Dispose(); }: enumerator List<T> mengimplementasikan IDisposable, sehingga pembersihan dijamin berjalan meskipun iterasi dihentikan di tengah jalan.

Bila kode manual di atas kita ganti dengan foreach (string nama in pesertaLomba), hasilnya identik: compiler menerjemahkan foreach menjadi bentuk GetEnumerator-while-Current-Dispose tersebut.

Kontrak dua interface ini yang kita isi pada bab Iterator Pattern: sebuah koleksi menyediakan enumerator, dan konsumen membaca isinya tanpa mengetahui struktur internal.