IEnumerable<T> adalah antarmuka yang menyatakan kemampuan sebuah objek untuk ditelusuri satu per satu, sedangkan kata kunci yield return mengubah sebuah metode menjadi iterator, yaitu metode yang menghasilkan barisan elemen satu demi satu tanpa membangun collection perantara. Topik ini membahas kontrak enumerasi IEnumerable<T>, cara menulis metode iterator, sifat penilaian yang malas (lazy), dan penghentian barisan dengan yield break. Setiap perilaku disertai contoh yang dijalankan beserta keluaran aslinya.
IEnumerable sebagai kontrak enumerasi#
Tipe yang mengimplementasikan IEnumerable<T> menyediakan satu metode GetEnumerator, dan metode itulah yang digunakan foreach untuk menelusuri elemen. Karena List<T>, array, dan hampir seluruh collection .NET mengimplementasikan antarmuka ini, satu pola penelusuran foreach berlaku untuk semuanya. Variabel bertipe IEnumerable<T> juga dapat menampung collection apa pun tanpa memedulikan tipe konkret di baliknya.
// Contoh: List ditampung sebagai IEnumerable lalu ditelusuri foreach
List<int> nilaiUjian = new() { 84, 71, 90 };
IEnumerable<int> barisan = nilaiUjian;
foreach (int nilai in barisan)
{
Console.WriteLine(nilai);
}84
71
90Penugasan IEnumerable<int> barisan = nilaiUjian tidak menyalin isi List, tetapi hanya menyimpan referensi ke objek yang sama dengan tipe yang dilihat pemanggil dipersempit menjadi kontrak enumerasi. Konsekuensinya, metode yang nilai kembaliannya IEnumerable<T> dapat mengembalikan List, array, atau barisan hasil perhitungan tanpa mengubah tanda tangannya, sehingga pemanggil tidak perlu mengetahui bentuk penyimpanan di baliknya. Array juga mengimplementasikan antarmuka ini, sehingga hasil saringan, data dari berkas, dan collection siap pakai dapat mengalir melalui satu tipe penampung yang sama. Ketiadaan metode tambahan pada kontrak tersebut bukan kekurangan, melainkan desain yang menempatkan pengolahan data pada operator LINQ, sedangkan kontraknya hanya menjamin penelusuran.
Metode iterator dengan yield return#
Metode yang mengandung yield return menjadi metode iterator dan nilai kembaliannya harus IEnumerable<T> atau antarmuka enumerasi lain. Setiap kali yield return dijalankan, satu elemen diserahkan kepada penelusur, kemudian eksekusi metode berhenti di posisi tersebut dan dilanjutkan kembali ketika elemen berikutnya diminta. Contoh berikut menyaring nilai ujian yang memenuhi ambang lulus.
// Contoh: metode iterator menyaring nilai yang memenuhi ambang lulus
IEnumerable<int> NilaiLulus(List<int> daftar)
{
foreach (int nilai in daftar)
{
if (nilai >= 75)
{
yield return nilai;
}
}
}
List<int> data = new() { 84, 60, 90, 55, 75 };
foreach (int nilai in NilaiLulus(data))
{
Console.WriteLine($"Lulus: {nilai}");
}Lulus: 84
Lulus: 90
Lulus: 75Metode NilaiLulus tidak pernah membangun List baru yang berisi hasil saringan. Kondisi nilai >= 75 diperiksa untuk setiap elemen sumber, dan hanya elemen yang lolos yang diserahkan melalui yield return. Angka 60 dan 55 tidak pernah diserahkan karena kondisi tidak terpenuhi, sehingga tidak ada collection sementara yang dialokasikan di sepanjang proses tersebut.
Satu metode iterator boleh memuat beberapa pernyataan yield return, dan barisan yang dihasilkannya mengikuti urutan pernyataan tersebut. Pernyataan yield return juga sah di dalam perulangan, sehingga jumlah elemen dapat ditentukan pada saat berjalan, seperti perulangan for pada contoh berikutnya. Kombinasi keduanya membuat iterator menjadi cara yang ringkas untuk membentuk barisan uji dan barisan data sintetis.
Penilaian yang malas#
Metode iterator dievaluasi secara lazy, yaitu badannya baru berjalan ketika penelusuran benar-benar dilakukan, bukan pada saat metodenya dipanggil. Pemanggilan metode iterator hanya membentuk objek barisan, sedangkan kode di dalamnya menunggu pernyataan foreach meminta elemen pertama. Contoh berikut menampilkan urutan kejadian tersebut dengan cetakan pada dua sisi, yaitu di dalam iterator dan di tempat elemen diterima.
// Contoh: badan iterator baru berjalan ketika foreach mulai menelusuri
Console.WriteLine("Sebelum memanggil metode iterator");
IEnumerable<int> barisan = BilanganGenap(3);
Console.WriteLine("Metode dipanggil, belum ada angka yang dicetak");
foreach (int angka in barisan)
{
Console.WriteLine($"Menerima {angka}");
}
IEnumerable<int> BilanganGenap(int jumlah)
{
for (int i = 1; i <= jumlah; i++)
{
Console.WriteLine($"Menghasilkan {2 * i}");
yield return 2 * i;
}
}Sebelum memanggil metode iterator
Metode dipanggil, belum ada angka yang dicetak
Menghasilkan 2
Menerima 2
Menghasilkan 4
Menerima 4
Menghasilkan 6
Menerima 6Baris Menghasilkan dan Menerima berselang-seling, yang menunjukkan bahwa kedua sisi bergantian berjalan per elemen. Badan iterator tidak berjalan sekali habis dari awal sampai akhir, melainkan berhenti pada setiap yield return sampai elemen berikutnya diminta. Sifat inilah yang menjadi dasar eksekusi tunda pada query LINQ yang dibahas pada topik tersendiri di modul ini.
Menghentikan barisan dengan yield break#
Pernyataan yield break mengakhiri barisan sebelum seluruh sumber habis ditelusuri. Setelah yield break dijalankan, penelusuran berperilaku seolah-olah elemen terakhir sudah tercapai, sehingga foreach yang membacanya berhenti dengan sempurna tanpa error. Contoh berikut menghentikan barisan ketika penanda ditemukan.
// Contoh: yield break menghentikan barisan sebelum seluruh sumber habis
List<string> kata = new() { "ali", "berhenti", "citra" };
foreach (string k in SampaiPenanda(kata))
{
Console.WriteLine($"Kata diterima: {k}");
}
IEnumerable<string> SampaiPenanda(List<string> sumber)
{
foreach (string k in sumber)
{
if (k == "berhenti")
{
yield break;
}
yield return k;
}
}Kata diterima: aliKata “citra” tidak pernah diserahkan karena yield break dijalankan sebelum iterator sampai ke elemen itu. Perbedaannya dengan return pada metode biasa terletak pada sifatnya, yaitu return mengakhiri metode secara total, sedangkan yield break hanya menandai akhir barisan yang sedang dihasilkan.
Contoh program utuh#
Program berikut merangkum ketiga pola di atas dalam satu berkas, yaitu penyaringan, barisan yang dievaluasi malas, dan penghentian lebih awal.
// Program contoh IEnumerable, yield return, dan yield break
// Metode iterator: menyaring nilai yang memenuhi ambang lulus
IEnumerable<int> NilaiLulus(List<int> daftar)
{
foreach (int nilai in daftar)
{
if (nilai >= 75)
{
yield return nilai;
}
}
}
// Metode iterator: badannya berjalan per elemen selama penelusuran
IEnumerable<int> BilanganGenap(int jumlah)
{
for (int i = 1; i <= jumlah; i++)
{
Console.WriteLine($"Menghasilkan {2 * i}");
yield return 2 * i;
}
}
// Metode iterator: berhenti lebih awal lewat yield break
IEnumerable<string> SampaiPenanda(List<string> sumber)
{
foreach (string k in sumber)
{
if (k == "berhenti")
{
yield break;
}
yield return k;
}
}
List<int> nilaiUjian = new() { 84, 60, 90, 55, 75 };
foreach (int nilai in NilaiLulus(nilaiUjian))
{
Console.WriteLine($"Lulus: {nilai}");
}
Console.WriteLine("Barisan genap:");
foreach (int angka in BilanganGenap(3))
{
Console.WriteLine($"Menerima {angka}");
}
List<string> kata = new() { "ali", "berhenti", "citra" };
Console.WriteLine("Sampai penanda:");
foreach (string k in SampaiPenanda(kata))
{
Console.WriteLine($"Kata diterima: {k}");
}Lulus: 84
Lulus: 90
Lulus: 75
Barisan genap:
Menghasilkan 2
Menerima 2
Menghasilkan 4
Menerima 4
Menghasilkan 6
Menerima 6
Sampai penanda:
Kata diterima: aliJalankan program tersebut dari folder topik dengan perintah berikut.
# aktifkan .NET yang terpasang di sandbox
export DOTNET_ROOT=/home/kal/.dotnet PATH=/home/kal/.dotnet:$PATH DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1
# jalankan program contoh dari folder topik
dotnet run --file Program.csLulus: 84
Lulus: 90
Lulus: 75
Barisan genap:
Menghasilkan 2
Menerima 2
Menghasilkan 4
Menerima 4
Menghasilkan 6
Menerima 6
Sampai penanda:
Kata diterima: aliKesalahan umum#
- Mengira badan metode iterator berjalan pada saat metodenya dipanggil. Pada kenyataannya badan baru berjalan saat penelusuran dimulai, sehingga efek samping seperti cetakan di dalamnya muncul lebih lambat daripada dugaan.
- Menempatkan
yield returndi dalam bloktryyang memiliki klausacatch. Kompilator menolaknya dengan error CS1626, dan perbaikannya adalah memindahkan penelusuran sumber keluar dari blok tersebut. - Menghitung
Count()padaIEnumerable<T>yang sumbernya besar secara berulang. Setiap hitungan menelusuri ulang seluruh barisan, sehingga hasil saringan yang digunakan berkali-kali sebaiknya dimaterialisasi menjadiListterlebih dahulu.
// tidak-dijalankan
// Contoh: yield return di dalam blok try ber-catch ditolak dengan error CS1626
IEnumerable<int> Barisan()
{
try
{
yield return 1;
}
catch (InvalidOperationException)
{
}
}Ringkasan#
IEnumerable<T>adalah kontrak enumerasi yang digunakanforeach, dan tipe konkret di baliknya tidak perlu diketahui pemanggil.yield returnmengubah metode menjadi iterator yang menyerahkan elemen satu demi satu tanpa collection perantara.- Badan iterator dievaluasi secara lazy, yaitu berjalan hanya ketika penelusuran dilakukan dan berhenti di setiap
yield return. yield breakmengakhiri barisan lebih awal, sedangkan kompilator menyusun mesin keadaan untuk mengelola posisi eksekusinya.
Referensi#
- yield statement — Microsoft Learn
— Rujukan resmi kata kunci
yield, mencakupyield return,yield break, dan batasannya. - IEnumerable
— Microsoft Learn — Rujukan API antarmuka enumerasi generik yang menjadi dasarforeachdan LINQ.