foreach menjalankan badannya sekali untuk setiap elemen sebuah koleksi, secara berurutan, tanpa indeks yang perlu kamu urus:
Output:
Welcome, Ava
Welcome, Noah
Welcome, Mia
Total: 255
[C][#][7]
Loop ini bekerja dengan cara yang sama pada array, List<T>, string (tipe elemennya char), HashSet<T>, query LINQ, atau urutan lainnya. Tipe yang dideklarasikan bisa berupa var, yang umum dipakai saat nama tipe elemennya panjang: foreach (var order in pendingOrders).
Apa yang dilakukan foreach di balik layar
foreach meminta sebuah enumerator dari koleksi lalu menelusurinya. Untuk List<int>, compiler mengubah loop tersebut kira-kira menjadi seperti ini:
List<int>.Enumerator e = scores.GetEnumerator();
try
{
while (e.MoveNext())
{
int score = e.Current;
// loop body
}
}
finally
{
e.Dispose();
}
Ada tiga akibatnya. Tipe apa pun yang punya method publik GetEnumerator() bisa dipakai, dan biasanya method ini tersedia dengan mengimplementasikan IEnumerable<T>. Enumerator tetap di-dispose walaupun badan loop melempar exception atau menjalankan break. Dan variabel elemen adalah salinan baru dari e.Current di setiap putaran, itulah sebabnya memberi nilai baru ke variabel itu tidak bisa mengubah koleksinya.
Tipe yang tidak punya GetEnumerator tidak bisa dipakai:
error CS1579: foreach statement cannot operate on variables of type 'Order' because 'Order' does not contain a public instance or extension definition for 'GetEnumerator'
Implementasikan IEnumerable<T> pada tipe tersebut, atau lakukan perulangan pada koleksi yang ada di dalamnya (order.Lines, bukan order). Kamu bisa menulis urutanmu sendiri dengan yield return, yang dibahas di halaman IEnumerable dan yield.
Perulangan pada Dictionary
Elemen sebuah dictionary adalah nilai KeyValuePair<TKey, TValue> dengan property Key dan Value:
Output:
apples: 12
pears: 0
kiwis: 30
<apples><pears><kiwis>
Units in stock: 42
30 kiwis
12 apples
0 pears
Dictionary tidak menjanjikan urutan enumerasi apa pun. Dalam praktiknya urutannya mengikuti urutan penambahan sampai kamu menghapus entri, tapi kode yang bergantung pada urutan sebaiknya mengurutkan secara eksplisit, seperti loop terakhir yang memakai OrderByDescending, atau memakai SortedDictionary.
Di .NET Core 2.0 ke atas (dan semua versi .NET modern), KeyValuePair punya method Deconstruct, jadi pasangan itu bisa langsung dipecah menjadi dua variabel di kepala loop:
// modern .NET: KeyValuePair deconstruction
foreach (var (fruit, count) in stock)
{
Console.WriteLine($"{fruit}: {count}");
}
Ini bentuk paling singkat dan yang akan kamu lihat di kode masa kini. Bentuk ini tidak bisa di-compile di .NET Framework, karena di sana KeyValuePair tidak punya Deconstruct; pakai item.Key dan item.Value di sana.
Mendapatkan indeks
foreach tidak punya indeks sendiri. Dua cara yang biasa dipakai untuk mendapatkannya adalah penghitung yang kamu kelola sendiri, atau overload Select dari LINQ yang mengirimkan indeks bersama setiap elemen:
Output:
1. Kenji
2. Sofia
3. Omar
index 0: Kenji
index 1: Sofia
index 2: Omar
Penghitung adalah cara yang paling hemat dan paling jelas. Kalau kamu mulai memakai indeks untuk membaca podium[i - 1] atau menulis podium[i] = ..., beralihlah ke perulangan for, yang memang dibuat untuk itu.
Variabel loop hanya bisa dibaca
Kamu tidak bisa memberi nilai baru ke variabel iterasi:
foreach (string name in names)
{
name = name.Trim();
// error CS1656: Cannot assign to 'name' because it is a 'foreach iteration variable'
}
Seandainya pun diperbolehkan, yang berubah hanya salinan lokalnya. Untuk mengubah setiap elemen, buat urutan baru (names.Select(n => n.Trim()).ToArray()) atau pakai perulangan for yang menulis names[i] = names[i].Trim().
Apa yang bisa kamu lakukan bergantung pada apakah elemennya class atau struct. Untuk class, variabel itu menyimpan referensi, jadi mengubah sebuah property akan mengubah objek yang ada di koleksi:
Output:
Order 1: shipped
Order 2: shipped
Seandainya Order adalah sebuah struct, pemberian nilai yang sama akan gagal dengan CS1654 (Cannot modify members of 'order' because it is a 'foreach iteration variable'), karena variabelnya adalah salinan nilai, bukan referensi ke nilai itu.
Mengubah koleksi: InvalidOperationException
Menambah atau menghapus elemen List atau Dictionary saat sebuah foreach sedang menelusurinya akan merusak enumerator. MoveNext berikutnya melempar InvalidOperationException dengan pesan "Collection was modified; enumeration operation may not execute.":
Output:
InvalidOperationException: collection was modified
milk, bread
milk, bread
Cara memperbaikinya, dari yang paling sederhana sampai yang paling fleksibel:
RemoveAll(predicate)padaList<T>menghapus semua elemen yang cocok dalam satu kali jalan.- Lakukan perulangan pada salinannya dengan
ToList()atauToArray(), lalu ubah koleksi aslinya dengan bebas. - Kumpulkan dulu, terapkan kemudian: selama loop, kumpulkan key yang akan dihapus di list terpisah, lalu hapus setelah loop selesai. Ini pola yang biasa dipakai untuk
Dictionary. - Perulangan
foryang berjalan mundur denganRemoveAt(i).
Dictionary punya satu pengecualian yang perlu kamu tahu. Sejak .NET Core 3.0, menimpa nilai dari key yang sudah ada (stock[key] = 0) atau menghapus entri selama foreach berjalan pada dictionary itu tidak lagi melempar exception, sedangkan menambah key baru tetap melemparnya. Di .NET Framework ketiganya melempar exception, jadi mengumpulkan key terlebih dahulu adalah versi yang bekerja di mana saja.
break, continue dan koleksi null
break dan continue bekerja seperti di loop lainnya:
Output:
reading 21
reading 23
no tags, no crash
Melakukan perulangan pada koleksi null akan melempar NullReferenceException saat GetEnumerator dipanggil. ?? Enumerable.Empty<T>() mengubah urutan yang mungkin null menjadi urutan kosong; lebih baik lagi, kembalikan koleksi kosong, bukan null, dari method buatanmu sendiri.
foreach dibandingkan List.ForEach
List<T> punya method ForEach(Action<T>): names.ForEach(n => Console.WriteLine(n));. Method ini tidak sama dengan loop. Badannya adalah lambda, jadi break, continue dan yield tidak tersedia, dan return hanya mengakhiri pemanggilan untuk elemen yang sedang diproses. Method ini hanya ada di List<T>, tidak di array atau urutan lain. Utamakan pernyataan foreach; pakai ForEach hanya untuk aksi satu baris yang sudah kamu punya dalam bentuk method.
Pertanyaan yang sering diajukan
Bagaimana cara melakukan perulangan pada Dictionary dengan foreach di C#?
Setiap elemen adalah KeyValuePair<TKey, TValue>: foreach (var pair in prices) Console.WriteLine($"{pair.Key}: {pair.Value}");. Di .NET Core 2.0 ke atas, kamu bisa mendekonstruksinya: foreach (var (name, price) in prices). Untuk melakukan perulangan hanya pada key atau value, pakai prices.Keys atau prices.Values.
Bagaimana cara mendapatkan indeks di perulangan foreach C#?
foreach tidak punya indeks bawaan. Simpan sebuah penghitung (int i = 0; sebelum loop, i++ di akhir badan loop), atau pasangkan setiap elemen dengan indeksnya memakai LINQ: foreach (var item in names.Select((name, index) => new { name, index })). Kalau kamu butuh indeks untuk menulis kembali ke koleksi atau membandingkan elemen yang bersebelahan, perulangan for lebih cocok.
Apa arti "Collection was modified; enumeration operation may not execute"?
Kamu menambah atau menghapus item di List, Dictionary atau koleksi lain saat sebuah foreach sedang melakukan enumerasi pada koleksi itu. Enumerator mendeteksi perubahan tersebut dan melempar InvalidOperationException di langkah berikutnya. Lakukan perulangan pada salinannya (foreach (var x in list.ToList())), kumpulkan perubahannya lalu terapkan setelah loop, pakai list.RemoveAll(...), atau pakai perulangan for yang berjalan mundur.
Kenapa saya tidak bisa memberi nilai baru ke variabel foreach di C#?
Variabel iterasi hanya bisa dibaca: name = name.Trim(); di dalam foreach (string name in names) adalah error CS1656. Memberi nilai baru ke variabel itu juga tidak akan mengubah koleksinya. Untuk mengganti elemen, pakai perulangan for dengan indeks, atau buat koleksi baru dengan Select. Mengubah property sebuah objek class (order.Status = ...) tetap diperbolehkan.
Apakah bisa keluar dari perulangan foreach di C#?
Bisa. break langsung mengakhiri loop dan continue melompat ke elemen berikutnya, sama persis seperti di for dan while. return keluar dari seluruh method. Method List<T>.ForEach berbeda: badannya adalah lambda, jadi break dan continue tidak tersedia di sana.