Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Yazdığınız hemen her program bir koleksiyon üzerinden geçiş yapma ihtiyacı duyacaktır. Koleksiyondaki her öğeyi inceleyen bir kod yazacaksınız.
Ayrıca, bu sınıfın öğeleri için yineleyici oluşturan yöntemler olan yineleyici yöntemleri de oluşturacaksınız. Yineleyici, özellikle listeler gibi kapsayıcılar üzerinde dolaşan bir nesnedir. Yineleyiciler şu için kullanılabilir:
- Koleksiyondaki her öğe üzerinde eylem gerçekleştirme.
- Özel bir koleksiyonun elemanlarını listeleme.
- LINQ veya diğer kitaplıkları genişletme.
- Verilerin yineleyici yöntemleri aracılığıyla verimli bir şekilde aktığı bir veri işlem hattı oluşturma.
C# dili hem oluşturma hem de kullanma dizileri için özellikler sağlar. Bu diziler zaman uyumlu veya zaman uyumsuz olarak üretilebilir ve kullanılabilir. Bu makalede bu özelliklere genel bir bakış sağlanır.
foreach ile yineleme
Bir koleksiyonu listelemek basittir: foreach
anahtar sözcüğü bir koleksiyonu numaralandırır ve koleksiyondaki her öğe için katıştırılmış deyimi bir kez yürütür:
foreach (var item in collection)
{
Console.WriteLine(item?.ToString());
}
Hepsi bu kadar. Bir koleksiyonun tüm içeriğini yinelemek için tek ihtiyacınız olan foreach
deyimidir. Ama foreach
ifade sihirli değil. Bir koleksiyonu yinelemek için gereken kodu oluşturmak için .NET core kitaplığında tanımlanan iki genel arabirime dayanır: IEnumerable<T>
ve IEnumerator<T>
. Bu mekanizma aşağıda daha ayrıntılı olarak açıklanmıştır.
Bu arabirimlerin her ikisi de genel olmayan karşılıklara sahiptir: IEnumerable
ve IEnumerator
.
Genel sürümler modern kod için tercih edilir.
Bir sıra zaman uyumsuz olarak oluşturulduğunda, sırayı zaman uyumsuz olarak tüketmek için await foreach
deyimini kullanabilirsiniz.
await foreach (var item in asyncSequence)
{
Console.WriteLine(item?.ToString());
}
Bir sıra bir System.Collections.Generic.IEnumerable<T> olduğunda foreach
kullanırsınız. Bir sıra bir System.Collections.Generic.IAsyncEnumerable<T> olduğunda await foreach
kullanırsınız. İkinci durumda, sıra zaman uyumsuz olarak oluşturulur.
Yineleyici yöntemleriyle numaralandırma kaynakları
C# dilinin bir diğer harika özelliği de numaralandırma için kaynak oluşturan yöntemler oluşturmanıza olanak tanır. Bu yöntemler yineleyici yöntemler olarak adlandırılır. Yineleyici yöntemi, istendiğinde bir dizideki nesnelerin nasıl oluşturulacağı tanımlar. Yineleyici yöntemi tanımlamak için bağlamsal anahtar sözcükleri kullanırsınız yield return
.
0 ile 9 arasında tamsayı dizisini oluşturmak için bu yöntemi yazabilirsiniz:
public IEnumerable<int> GetSingleDigitNumbers()
{
yield return 0;
yield return 1;
yield return 2;
yield return 3;
yield return 4;
yield return 5;
yield return 6;
yield return 7;
yield return 8;
yield return 9;
}
Yukarıdaki kod, yield return
ve yield return
ifadelerinin ayrı ayrı kullanıldığını göstererek bir yineleyici yönteminde birden fazla ayrık ifade kullanabileceğiniz gerçeğini vurgular. Yineleyici yönteminin kodunu basitleştirmek için diğer dil yapılarını kullanabilirsiniz (ve genellikle yapabilirsiniz). Aşağıdaki yöntem tanımı tam olarak aynı sayı dizisini oluşturur:
public IEnumerable<int> GetSingleDigitNumbersLoop()
{
int index = 0;
while (index < 10)
yield return index++;
}
Birini veya diğerini seçmek zorunda değilsin. Yönteminizin gereksinimlerini karşılamak için gereken sayıda yield return
deyiminiz olabilir:
public IEnumerable<int> GetSetsOfNumbers()
{
int index = 0;
while (index < 10)
yield return index++;
yield return 50;
index = 100;
while (index < 110)
yield return index++;
}
Önceki örneklerin hepsinin eş zamanlı olmayan bir karşılığı olacaktır. Her durumda dönüş türünü IEnumerable<T>
ile IAsyncEnumerable<T>
değiştirirsiniz. Örneğin, önceki örnekte aşağıdaki zaman uyumsuz sürüm bulunur:
public async IAsyncEnumerable<int> GetSetsOfNumbersAsync()
{
int index = 0;
while (index < 10)
yield return index++;
await Task.Delay(500);
yield return 50;
await Task.Delay(500);
index = 100;
while (index < 110)
yield return index++;
}
Bu, hem zaman uyumlu hem de zaman uyumsuz yineleyiciler için söz dizimidir. Şimdi gerçek bir dünya örneğini ele alalım. Bir IoT projesinde olduğunuzu ve cihaz algılayıcılarının çok büyük bir veri akışı oluşturduğunu hayal edin. Veriler için bir his elde etmek için her N. veri öğesini örnekleyen bir yöntem yazabilirsiniz. Bu küçük yineleyici yöntem işe yarar.
public static IEnumerable<T> Sample<T>(this IEnumerable<T> sourceSequence, int interval)
{
int index = 0;
foreach (T item in sourceSequence)
{
if (index++ % interval == 0)
yield return item;
}
}
IoT cihazından okuma zaman uyumsuz bir dizi oluşturursa, aşağıdaki yöntemin gösterdiği gibi yöntemini değiştirirsiniz:
public static async IAsyncEnumerable<T> Sample<T>(this IAsyncEnumerable<T> sourceSequence, int interval)
{
int index = 0;
await foreach (T item in sourceSequence)
{
if (index++ % interval == 0)
yield return item;
}
}
Yineleyici yöntemlerinde önemli bir kısıtlama vardır: aynı yöntemde hem return
ifadesi hem de yield return
ifadesi olamaz. Aşağıdaki kod derlenmez:
public IEnumerable<int> GetSingleDigitNumbers()
{
int index = 0;
while (index < 10)
yield return index++;
yield return 50;
// generates a compile time error:
var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
return items;
}
Bu kısıtlama normalde bir sorun değildir. Yöntemin tamamında yield return
kullanma veya özgün yöntemi birden fazla yönteme ayırarak bazı yöntemlerde return
ve diğerlerinde yield return
kullanma seçeneğiniz vardır.
Son yöntemi her yerde yield return
kullanmanıza izin vermek için yönteminizi biraz değiştirebilirsiniz.
public IEnumerable<int> GetFirstDecile()
{
int index = 0;
while (index < 10)
yield return index++;
yield return 50;
var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
foreach (var item in items)
yield return item;
}
Bazen doğru yanıt, yineleyici yöntemini iki farklı yönteme bölmektir. Birincisi return
kullanan ve ikincisi yield return
kullanan. Boolean ifadesine göre boş bir koleksiyon veya ilk beş tek sayıyı döndürmek isteyebileceğiniz bir durum düşünün. Bunu şu iki yöntem olarak yazabilirsiniz:
public IEnumerable<int> GetSingleDigitOddNumbers(bool getCollection)
{
if (getCollection == false)
return new int[0];
else
return IteratorMethod();
}
private IEnumerable<int> IteratorMethod()
{
int index = 0;
while (index < 10)
{
if (index % 2 == 1)
yield return index;
index++;
}
}
Yukarıdaki yöntemlere bakın. birincisi, boş bir koleksiyon veya ikinci yöntem tarafından oluşturulan yineleyiciyi döndürmek için standart return
deyimini kullanır. İkinci yöntem, istenen diziyi oluşturmak için yield return
deyimini kullanır.
Daha derinlemesine bir bakış foreach
foreach
deyimi, IEnumerable<T>
ve IEnumerator<T>
arabirimlerini kullanarak bir koleksiyonun tüm öğeleri üzerinde yinelemek için standart bir deyime genişletilir. Ayrıca, geliştiricilerin kaynakları düzgün yönetmeyerek yaptığı hataları da en aza indirir.
Derleyici, ilk örnekte gösterilen döngüleri foreach
şu yapıya benzer bir şeye çevirir:
IEnumerator<int> enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
Derleyici tarafından üretilen tam kod daha karmaşıktır ve GetEnumerator()
tarafından döndürülen nesne IDisposable
arabirimini uyguladığı durumları işler. Tam genişletme aşağıdakine benzer bir kod oluşturur:
{
var enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
}
finally
{
// dispose of enumerator.
}
}
Derleyici, ilk zaman uyumsuz örneği şu yapıya benzer bir şeye çevirir:
{
var enumerator = collection.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
}
finally
{
// dispose of async enumerator.
}
}
Sayacın veya sıralayıcının yönetimi, enumerator
türünün özelliklerine bağlıdır. Genel eşzamanlı durumda, finally
yan tümcesi şu şekilde genişletilir:
finally
{
(enumerator as IDisposable)?.Dispose();
}
Genel zaman uyumsuz durum şu şekildedir:
finally
{
if (enumerator is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync();
}
Ancak, enumerator
türü korumalı bir türse ve enumerator
türünden IDisposable
veya IAsyncDisposable
türüne örtük bir dönüştürme yoksa, finally
ifadesi boş bir bloğa genişletilir:
finally
{
}
enumerator
'den IDisposable
türüne özellikle bir dönüştürme varsa ve enumerator
boş değer atanamayan bir değer türüyse, yan tümce finally
şu şekilde genişletilir:
finally
{
((IDisposable)enumerator).Dispose();
}
Neyse ki tüm bu ayrıntıları hatırlamanıza gerek yok.
foreach
ifadesi tüm bu nüansları sizin için halleder. Derleyici, bu yapılardan herhangi biri için doğru kodu oluşturur.