yield
Anahtar kelime bir oluşturmanıza olanak sağlar IEnumerable<T>
bir formdan yineleyici bloğu . Bu yineleyici bloğu ertelenmiş yürütmeyi destekler ve kavramı bilmiyorsanız neredeyse büyülü görünebilir. Ancak, günün sonunda garip hileler olmadan çalışan sadece kod.
Bir yineleyici blok, derleyicinin numaralandırmanın numaralandırılmasının ne kadar ilerlediğini takip eden bir durum makinesi ürettiği sözdizimsel şeker olarak tanımlanabilir. Bir numaralandırmayı numaralandırmak için genellikle bir foreach
döngü kullanırsınız . Bununla birlikte, bir foreach
döngü aynı zamanda sözdizimsel şekerdir. Yani gerçek koddan çıkarılmış iki soyutlamasınız, bu yüzden başlangıçta hepsinin birlikte nasıl çalıştığını anlamak zor olabilir.
Çok basit bir yineleyici bloğunuz olduğunu varsayın:
IEnumerable<int> IteratorBlock()
{
Console.WriteLine("Begin");
yield return 1;
Console.WriteLine("After 1");
yield return 2;
Console.WriteLine("After 2");
yield return 42;
Console.WriteLine("End");
}
Gerçek yineleyici blokları genellikle koşullara ve döngülere sahiptir, ancak koşulları kontrol ettiğinizde ve döngüleri kaldırdığınızda yield
, diğer kodlarla araya eklenmiş ifadeler olarak son bulurlar .
Yineleyici bloğunu numaralandırmak için bir foreach
döngü kullanılır:
foreach (var i in IteratorBlock())
Console.WriteLine(i);
İşte çıktı (burada sürpriz yok):
Başla
1
1'den sonra
2
2 Sonrası
42
Son
Yukarıda belirtildiği gibi foreach
sözdizimsel şeker:
IEnumerator<int> enumerator = null;
try
{
enumerator = IteratorBlock().GetEnumerator();
while (enumerator.MoveNext())
{
var i = enumerator.Current;
Console.WriteLine(i);
}
}
finally
{
enumerator?.Dispose();
}
Bu çözmek için bir soyutlama kaldırıldı ile bir dizi diyagramı sandık:
Derleyici tarafından üretilen durum makinesi de numaralandırıcıyı uygular, ancak şemayı daha açık hale getirmek için bunları ayrı örnekler olarak gösterdim. (Durum makinesi başka bir iş parçacığından numaralandırıldığında, aslında ayrı örnekler alırsınız, ancak bu ayrıntı burada önemli değildir.)
Yineleyicinizi her çağırdığınızda durum makinesinin yeni bir örneği oluşturulur. Ancak, yineleyici bloğundaki kodlarınızın hiçbiri enumerator.MoveNext()
ilk kez yürütülene kadar yürütülmez. Ertelenmiş yürütme böyle çalışır. İşte (oldukça aptalca) bir örnek:
var evenNumbers = IteratorBlock().Where(i => i%2 == 0);
Bu noktada yineleyici yürütülmemiştir. Where
Fıkra yeni oluşturur IEnumerable<T>
sarar o IEnumerable<T>
tarafından döndürülen IteratorBlock
ancak bu enumerable sayılan henüz bulunmuyor. Bir foreach
döngü yürüttüğünüzde bu gerçekleşir :
foreach (var evenNumber in evenNumbers)
Console.WriteLine(eventNumber);
Numaralandırmayı iki kez numaralandırırsanız, her seferinde durum makinesinin yeni bir örneği oluşturulur ve yineleyici bloğunuz aynı kodu iki kez yürütür.
LINQ yöntemleri gibi Bildirimi olduğunu ToList()
, ToArray()
, First()
, Count()
vb kullanacak foreach
enumerator numaralandırma döngü. Örneğin ToList()
, numaralandırılabilir öğenin tüm öğelerini numaralandıracak ve bir listede saklayacaktır. Artık yineleyicinin tüm öğelerini yineleyici bloğu tekrar yürütmeden almak için listeye erişebilirsiniz. Numaralandırılabilir öğelerin öğelerini üretmek için CPU kullanımı ile numaralandırma öğelerini depolamak gibi bellekler gibi yöntemleri kullanırken bunlara birden çok kez erişmek arasında bir denge vardır ToList()
.