foreach, yönetilecek bir indeks olmadan, bir koleksiyonun her elemanı için gövdesini sırayla bir kez çalıştırır:
Çıktı:
Welcome, Ava
Welcome, Noah
Welcome, Mia
Total: 255
[C][#][7]
Döngü bir dizide, bir List<T>'de, bir string'de (eleman tipi char), bir HashSet<T>'de, bir LINQ sorgusunda ya da başka herhangi bir dizide aynı şekilde çalışır. Bildirilen tip var olabilir; eleman tipi uzun olduğunda bu yaygındır: foreach (var order in pendingOrders).
foreach Perde Arkasında Ne Yapar
foreach koleksiyondan bir enumerator ister ve onu dolaşır. Bir List<int> için derleyici döngüyü kabaca şuna çevirir:
List<int>.Enumerator e = scores.GetEnumerator();
try
{
while (e.MoveNext())
{
int score = e.Current;
// loop body
}
}
finally
{
e.Dispose();
}
Bundan üç sonuç çıkar. Public bir GetEnumerator() metodu olan her tip çalışır; bu normalde IEnumerable<T> uygulanarak sağlanır. Enumerator, gövde istisna fırlatsa ya da break yapsa bile dispose edilir. Ve eleman değişkeni her geçişte e.Current'ın taze bir kopyasıdır; ona atama yapmanın koleksiyonu değiştirememesinin nedeni budur.
GetEnumerator olmayan bir tip kullanılamaz:
error CS1579: foreach statement cannot operate on variables of type 'Order' because 'Order' does not contain a public instance or extension definition for 'GetEnumerator'
Tipe IEnumerable<T> uygulayın ya da içerdiği koleksiyon üzerinde dönün (order yerine order.Lines). IEnumerable ve yield sayfasında ele alınan yield return ile kendi dizilerinizi yazabilirsiniz.
Bir Dictionary Üzerinde Döngü
Bir dictionary'nin elemanları Key ve Value property'leri olan KeyValuePair<TKey, TValue> değerleridir:
Çıktı:
apples: 12
pears: 0
kiwis: 30
<apples><pears><kiwis>
Units in stock: 42
30 kiwis
12 apples
0 pears
Bir Dictionary herhangi bir dolaşma sırası vaat etmez. Uygulamada girdileri silene kadar ekleme sırasını izler, ama sıraya bağlı kod son döngünün OrderByDescending ile yaptığı gibi açıkça sıralamalı ya da SortedDictionary kullanmalıdır.
.NET Core 2.0 ve sonrasında (ve her modern .NET'te) KeyValuePair'in bir Deconstruct metodu vardır, bu yüzden çift doğrudan döngü başlığında iki değişkene ayrılabilir:
// modern .NET: KeyValuePair deconstruction
foreach (var (fruit, count) in stock)
{
Console.WriteLine($"{fruit}: {count}");
}
Bu en kısa biçimdir ve güncel kodda göreceğiniz biçimdir. KeyValuePair'in Deconstruct'ı olmayan .NET Framework'te derlenmez; orada item.Key ve item.Value kullanın.
İndeksi Almak
foreach'in kendi indeksi yoktur. Bir tane almanın iki olağan yolu, sizin tuttuğunuz bir sayaç ya da LINQ'in her elemanla birlikte indeksi de geçiren Select overload'udur:
Çıktı:
1. Kenji
2. Sofia
3. Omar
index 0: Kenji
index 1: Sofia
index 2: Omar
Sayaç en ucuz ve en anlaşılır olanıdır. İndeksi podium[i - 1]'i okumak ya da podium[i] = ... yazmak için kullandığınızı fark ederseniz tam olarak bunun için yapılmış bir for döngüsüne geçin.
Döngü Değişkeni Salt Okunurdur
Yineleme değişkenine atama yapamazsınız:
foreach (string name in names)
{
name = name.Trim();
// error CS1656: Cannot assign to 'name' because it is a 'foreach iteration variable'
}
İzin verilseydi bile yalnızca yerel bir kopyayı değiştirirdi. Her elemanı dönüştürmek için yeni bir dizi oluşturun (names.Select(n => n.Trim()).ToArray()) ya da names[i] = names[i].Trim() yazan bir for döngüsü kullanın.
Yapabileceğiniz şey, elemanın bir sınıf mı yoksa bir struct mı olduğuna bağlıdır. Bir sınıf için değişken bir referans tutar, bu yüzden bir property'yi değiştirmek koleksiyondaki nesneyi değiştirir:
Çıktı:
Order 1: shipped
Order 2: shipped
Order bir struct olsaydı, aynı atama CS1654 (Cannot modify members of 'order' because it is a 'foreach iteration variable') ile başarısız olurdu, çünkü değişken değere bir referans değil, değerin bir kopyasıdır.
Koleksiyonu Değiştirmek: InvalidOperationException
Bir foreach onu dolaşırken bir List'in ya da Dictionary'nin elemanlarını eklemek ya da silmek enumerator'ı bozar. Sonraki MoveNext, "Collection was modified; enumeration operation may not execute." mesajıyla InvalidOperationException fırlatır:
Çıktı:
InvalidOperationException: collection was modified
milk, bread
milk, bread
En basitinden en esnekine çözümler:
- Bir
List<T>üzerindeRemoveAll(predicate)her eşleşmeyi tek geçişte siler. ToList()ya daToArray()ile bir kopya üzerinde dönün, sonra orijinali serbestçe değiştirin.- Önce toplayın, sonra uygulayın: döngü sırasında silinecek anahtarları ayrı bir listede toplayın, sonra onları döngüden sonra silin. Bu, bir
Dictionaryiçin olağan kalıptır. RemoveAt(i)ile geriye doğru birfordöngüsü.
Dictionary'nin bilmeye değer bir istisnası vardır. .NET Core 3.0'dan beri, üzerinde foreach dönerken mevcut bir anahtarın değerinin üzerine yazmak (stock[key] = 0) ya da girdileri silmek artık istisna fırlatmaz; yeni bir anahtar eklemek ise hâlâ fırlatır. .NET Framework'te üçü de fırlatır, bu yüzden her yerde çalışan sürüm önce anahtarları toplamaktır.
break, continue ve Null Koleksiyonlar
break ve continue diğer her döngüdeki gibi davranır:
Çıktı:
reading 21
reading 23
no tags, no crash
null bir koleksiyon üzerinde döngü kurmak, GetEnumerator çağrıldığında NullReferenceException fırlatır. ?? Enumerable.Empty<T>() null olabilecek bir diziyi boş bir diziye çevirir; daha da iyisi, kendi metotlarınızdan null yerine boş koleksiyonlar döndürün.
foreach ile List.ForEach
List<T>'nin bir ForEach(Action<T>) metodu vardır: names.ForEach(n => Console.WriteLine(n));. Döngüyle aynı şey değildir. Gövdesi bir lambda'dır, bu yüzden break, continue ve yield kullanılamaz ve return yalnızca geçerli elemanın çağrısını bitirir. Diziler ya da diğer diziler üzerinde değil, yalnızca List<T> üzerinde vardır. foreach ifadesini tercih edin; ForEach'i yalnızca zaten bir metot olarak elinizde bulunan tek satırlık bir eylem için kullanın.
Sıkça Sorulan Sorular
C#'ta foreach ile bir Dictionary nasıl dolaşılır?
Her eleman bir KeyValuePair<TKey, TValue>'dur: foreach (var pair in prices) Console.WriteLine($"{pair.Key}: {pair.Value}");. .NET Core 2.0 ve sonrasında onu ayrıştırabilirsiniz: foreach (var (name, price) in prices). Yalnızca anahtarlar ya da değerler üzerinde dönmek için prices.Keys ya da prices.Values kullanın.
C# foreach döngüsünde indeks nasıl alınır?
foreach'in yerleşik bir indeksi yoktur. Bir sayaç tutun (döngüden önce int i = 0;, gövdenin sonunda i++) ya da LINQ ile her elemanı indeksiyle birlikte yansıtın: foreach (var item in names.Select((name, index) => new { name, index })). İndekse geri yazmak ya da komşuları karşılaştırmak için ihtiyacınız varsa bir for döngüsü daha uygundur.
"Collection was modified; enumeration operation may not execute" ne demek?
Bir foreach onu dolaşırken bir List'e, Dictionary'ye ya da başka bir koleksiyona öğe eklediniz ya da ondan öğe sildiniz. Enumerator değişikliği fark eder ve sonraki adımda InvalidOperationException fırlatır. Bir kopya üzerinde dönün (foreach (var x in list.ToList())), değişiklikleri toplayıp döngüden sonra uygulayın, list.RemoveAll(...) kullanın ya da geriye doğru bir for döngüsü kullanın.
C#'ta foreach değişkenine neden atama yapamıyorum?
Yineleme değişkeni salt okunurdur: foreach (string name in names) içinde name = name.Trim(); CS1656 hatasıdır. Atama yapmak zaten koleksiyonu değiştirmezdi. Elemanları değiştirmek için indeksli bir for döngüsü kullanın ya da Select ile yeni bir koleksiyon oluşturun. Bir sınıf nesnesinin property'sini değiştirmeye (order.Status = ...) izin verilir.
C#'ta bir foreach döngüsünden çıkılabilir mi?
Evet. break döngüyü hemen bitirir, continue sonraki elemana atlar; tıpkı for ve while'daki gibi. return tüm metottan çıkar. List<T>.ForEach metodu farklıdır: gövdesi bir lambda'dır, bu yüzden orada break ve continue kullanılamaz.