İlkay İlknur

just a developer...

C# Iterators,Yield Keywordü ve Arka Planda Neler Oluyor ?

Merhaba Arkadaşlar,

Bu yazımızda sizlerle C# tarafındaki iterator yapısını incelemeye çalışacağız. Iteratorlar, array veya koleksiyonlar üzerinde yield keyword’ü kullanılarak içerlerindeki elemanlar üzerinde özel iterasyonlar yapmamızı sağlayan yapılardır. Iterator’lar bir metot olarak geliştirilebileceği gibi aynı zamanda bir property’nin get erişimcisi olarak ya da bir operator olarak ta geliştirilebilmektedir.

C# tarafında aslında iterasyon dediğimizde aklımıza gelen ilk yapı foreach döngüleri olmakta. foreach döngüleri ile bir kolleksiyon veya array içerisindeki elemanlara erişebiliyoruz ve istediğimiz işlemleri gerçekleştirebiliyoruz. Foreach döngüsünün arka planına baktığımızda da aslında bir iterator yapısının kullanıldığını görüyor olacağız. Çünkü iteratorlar foreach döngüleri tarafından kullanılırlar ve işlem yaptıkları kolleksiyonlar veya arrayler içerisindeki elemanları foreach döngüsüne iletirler.

Bir iterator yaratmak için öncelikli olarak bilmemiz gereken iteratorların dönüş tiplerinin IEnumerable, IEnumerator, IEnumerable<T> veya IEnumerator<T> interfacelerinden biri olması gerektiğidir. Zaten foreach döngüsünün yapısına aşina iseniz bu kuralı biliyor olduğunuzu düşünüyorum. ;) Iteratorlar ile ilgili bilmemiz gereken ikinci ve bence en önemli nokta yield keyword’ü. Bu keyword için özel bir başlık açmamızda fayda var. :)

Yield Keyword’ü Nedir ? Ne Yapar ?

Yield keyword’ünün kullanımına baktığımızda aslında arkasında bir compiler sihrinin yattığını görüyoruz. Bu nedenle de belki de yield keyword’ü genel olarak pek fazla developer tarafından bilinmemekte. Hatta bunu bilen şirketler iş görüşmelerinde yield keyword’ünün işlevini adaylara soru olarak yöneltmekte. :)

Yield keyword’ünün kullanımını incelediğimizde iki farklı kullanım şeklinin olduğunu görmekteyiz.

Bunlar,

  • yield return <ifade>
  • yield break

Yield return ifadesi ile iterator’a çağrı yapılan foreach döngüsüne bir eleman döndürülürken yield break ifadesi ile de artık bulunan iterator içerisindeki iterasyonun sona erdiği bilgisi iterator’ı çağıran foreach döngüsüne iletilmekte. Şimdi isterseniz basit bir iterator metot geliştirelim ve foreach döngüsü ile de geliştirdiğimiz iterator’ı kullanalım.

İlk olarak basit bir örnekten başlamak adına bize haftanın günlerini döndüren bir iterator metot geliştirelim.

static IEnumerable<string> GetDays()
{
 yield return "Pazartesi";
 yield return "Salı";
 yield return "Çarşamba";
 yield return "Perşembe";
 yield return "Cuma";
 yield return "Cumartesi";
 yield return "Pazar";
}
Geliştirdiğimiz iterator metodunu kullanacak olan foreach döngüsü ise şu şekilde
static void Main(string[] args)
{
 foreach (string day in GetDays())
   {
 Console.WriteLine(day);             
 }
}
Şimdi Console uygulaması olarak geliştirdiğimiz uygulamayı çalıştıralım ekranda oluşan çıktıları inceleyelim.

İşte yield keyword’ünün tüm sihri bu ekran görüntüsü ile başlamakta. Yukarıda yazmış olduğumuz GetDays isimli metodunun içerisine baktığımızda alt alta yield return ifadesini kullandığımızı görmekteyiz. Aslında var olan bilgilerimiz ile return ifadesinin bu şekilde kullanılmasının mümkün olmadığını biliyoruz. Peki ne oluyor da return ifadesinin önüne yield keyword’ü geldiğinde bu kullanım geçerli oluyor ? İşte bu sorunun altında aslında yield keyword’ünün işlevi yatıyor.

Compiler yield keyword’ünü gördüğü anda bu keyword’ün bulunduğu bloğun bir iterator bloğu olduğunu algılamakta. İşte bu noktada foreach döngüsü tarafından GetDays isimli iterator metodu her çağrıldığında metot içerisindeki bir yield return ifadesi çalıştırıldıktan sonra ilgili değer foreach ifadesine döndürülmeden önce compiler, iterator metodun kaldığı yeri saklamakta. Daha sonra ise foreach ifadesinden iterator metoduna gelen her istekte iterator metot baştan başlamak yerine kaldığı yerden işletilmekte. Böylece de yukarıdaki ekran görüntüsünde gördüğünüz sonuçları alabilmekteyiz.

Şimdi sıradaki örneğimiz ise aslında biraz daha gerçek kullanıma uygun. Senaryomuzda 10 elemanlı bir integer array’i bulunmakta. Uygulamamızı çalıştırmadan önce bu array’i 1 ile 30 arasındaki sayılar ile rastgele dolduracağız. Daha sonra ise geliştireceğimiz iterator ile bu array içerisinde bulunan ilk 2 sıradaki çift sayıları elde edeceğiz. Iterator’ı kullanan foreach döngüsüne ikinci kez çift sayısı döndürdükten sonra ise iterasyonun sonuna geldiğimizi foreach döngüsüne bildiriyor olacağız. Öncelikle integer array’i rastgele sayılarla dolduracak olan metodun geliştirimini yapalım.

private static void FillArray()
{
 Random rnd = new Random();
 numbers = new int[10];
 for (int i = 0; i < numbers.Length; i++)
 {
 numbers[i] = rnd.Next(1, 30);
 }
}
 

Array içerisinde bulunan ilk 2 sıradaki çift sayıları elde edeceğimizi iterator metodun geliştirimi ise şu şekilde.

static IEnumerable<int> GetTwoEvenNumbers()
{
 int evenNumber = 0;
 for (int i = 0; i < numbers.Length; i++)
 {
 if (evenNumber == 2)
 yield break;
 if (numbers[i] % 2 == 0)
 {
 evenNumber++;
 yield return numbers[i];
 }
 }
}
 

Son olarak ise iterator'ı kullanacak olan foreach ifadesinin de bulunduğu Main metodumuzu geliştirelim ve uygulamayı çalıştıralım.

static int[] numbers = null;
static void Main(string[] args)
{
 FillArray();
 foreach (int number in GetTwoEvenNumbers())
 {
 Console.WriteLine(number);
 }
}

Evet gördüğünüz gibi iterator metodu ile istediğimiz sonuçları elde edebildik. Şimdi isterseniz gelin yazmış olduğumuz GetTwoEvenNumbers isimli metodu inceleyelim. Metot içerisinde öncelikle iterator bloğu içerisinden döndürmüş olduğumuz çift sayıların sayısının tutmak amacıyla evenNumber isimli bir değişken tanımladık. Bu değişkenin değerini array içerisindeki çift sayıyı yield return ile döndürmeden hemen önce bir arttırdık. Son olarak ise for döngüsü içerisinde döndürdüğümüz çift sayıların sayısının kontrolünü yaptık ve eğer çift sayıların sayısı 2 ise iterasyonun sonlandırılması gerektiğini anlayıp yield break ifadesini kullandık.

Yield keyword’ünün kullanımını daha iyi kavradıkça aslında uygulamalarımız içerisinde pek çok noktada yield keyword’ünü kullanabileceğinizi farkediyor olacaksınız. Ayrıca şu anda .NET Framework içerisinde bulunan LINQ ifadelerindeki deferred execution yapısının alt yapısında iteratorların ve dolayısıyla da yield keyword’ünün yattığını söyleyebilirim.

Yazımızın bu kısmına kadar sizlerle iteratorları ve yield keyword’ünün işlevini incelemeye çalıştık. Ancak yield keyword’ünün bulunduğu bölümlerde gördüğünüz üzere bizim bildiğimiz akışlardan farklı bir akış var. Peki bu nasıl mümkün oluyor ? Şimdi bu konuyu incelemeye çalışalım.

Yield – Arka Planda Neler Oluyor ?

Yazımızın başında yield keyword’ünün arkasında aslında bir compiler sihrinin yattığından bahsetmiştim. Şimdi isterseniz bu sihri keşfetmeye başlayalım. Keşif için yazımızın başındaki günleri döndüren iterator’ı kullanıyor olacağız. Öncelikle isterseniz keşfimizi biraz daha kolaylaştırmak için iterator metot içerisinde 1-2 ufak değişiklik yapalım.

static IEnumerator GetDays()
{
 Console.WriteLine("Hafta başlıyor");
 yield return "Pazartesi";
 yield return "Salı";
 yield return "Çarşamba";
 Console.WriteLine("Hafta ortası");
 yield return "Perşembe";
 yield return "Cuma";
 yield return "Cumartesi";
 yield return "Pazar";
 Console.WriteLine("Hafta sonlanıyor...");
}
 

Yazımızın ilk paragraflarında bir iterator’ın dönüş tipinin IEnumerable, IEnumerable<T>, IEnumerator veya IEnumerator<T> interfacelerinden biri olması gerektiğinden bahsetmiştik. Biz ilk olarak IEnumerator tipini döndüren iterator metodunu inceliyor olacağız. Şimdi kodumuzu derleyelim ve Reflector aracılığıyla compiler’ın bizim için neler yaptığını incelemeye başlayalım. :)

internal class Program { // Methods private static IEnumerator GetDays() { return new <GetDays>d__0(0); } // Nested Types [CompilerGenerated] private sealed class <GetDays>d__0 : IEnumerator<object>, IEnumerator, IDisposable { // Fields private bool $__disposing; private int <>1__state; private object <>2__current; // Methods [DebuggerHidden] public <GetDays>d__0(int <>1__state) { this.<>1__state = <>1__state; } private bool MoveNext() { try { bool $__doFinallyBodies = true; switch (this.<>1__state) { case 1: if (this.$__disposing) { return false; } this.<>1__state = 0; this.<>2__current = "Salı"; this.<>1__state = 2; $__doFinallyBodies = false; return true; case 2: if (this.$__disposing) { return false; } this.<>1__state = 0; this.<>2__current = "\x00c7arşamba"; this.<>1__state = 3; $__doFinallyBodies = false; return true; case 3: if (this.$__disposing) { return false; } this.<>1__state = 0; Console.WriteLine("Hafta ortası"); this.<>2__current = "Perşembe"; this.<>1__state = 4; $__doFinallyBodies = false; return true; case 4: if (this.$__disposing) { return false; } this.<>1__state = 0; this.<>2__current = "Cuma"; this.<>1__state = 5; $__doFinallyBodies = false; return true; case 5: if (this.$__disposing) { return false; } this.<>1__state = 0; this.<>2__current = "Cumartesi"; this.<>1__state = 6; $__doFinallyBodies = false; return true; case 6: if (this.$__disposing) { return false; } this.<>1__state = 0; this.<>2__current = "Pazar"; this.<>1__state = 7; $__doFinallyBodies = false; return true; case 7: break; case -1: return false; default: if (this.$__disposing) { return false; } Console.WriteLine("Hafta başlıyor"); this.<>2__current = "Pazartesi"; this.<>1__state = 1; $__doFinallyBodies = false; return true; } if (this.$__disposing) { return false; } this.<>1__state = 0; Console.WriteLine("Hafta sonlanıyor..."); } catch (Exception) { this.<>1__state = -1; throw; } this.<>1__state = -1; return false; } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] void IDisposable.Dispose() { this.$__disposing = true; this.MoveNext(); this.<>1__state = -1; } // Properties object IEnumerator<object>.Current { [DebuggerHidden] get { return this.<>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return this.<>2__current; } } } }

Evet şu anda şok olmuş durumda olduğunuzu düşünüyorum. Nasıl oluyorda yazdığımız 10 satırlık metot bu şekilde compiler tarafından yeniden yazılıyor. İşte işin sihri burada yatıyor. Haydi biraz daha derinlere inelim.

  • Öncelikle compiler tarafından yarattığımız iterator metot için <GetDays>d__0 isimli bir inner –class yaratılıyor.
  • Yaratılan inner-class’ın implemente ettiği interface’leri incelediğimizde IEnumerator,IEnumerator<object> ve IDisposable olduğunu görüyoruz. foreach döngüsü sonlandıktan sonra otomatik olarak IDisposable interface’i içerisinde bulunan Dispose metodunu çağırmakta. Böylece döngünün kullanmış olduğu iteratorın otomatik olarak yok edilmesi sağlanmakta. Ayrıca developerların using ifadesi kullanarak otomatik olarak kendi kontrollerinde kullandıkları iteratorları bu şekilde yok etmeleri de mümkün hale gelmekte.
  • Ayrıca yaratmış olduğumuz iterator metodumuzun gövdesinin tamamen kaldırıldığını ve sadece compiler tarafından yaratılmış olan sınıftan bir nesne örneği yaratıp döndürecek şekilde yeninden yazıldığını görüyoruz. Bu arada nesne örneği yarattığı sırada parametre olarak 0 gönderdiğini de mutlaka aklımızda tutalım ;)
  • Yaratılan inner-class içerisinde 3 adet private değişken görmekteyiz. Bu değişkenler sırasıyla iterator’ın dispose edilme durumunda olup olmadığını, iterator’ın durumunu ve o anda iterator içerisindeki mevcut elemanı tutmakta.
  • Constructor metot içerisinde dışarıdan alınan parametreye göre ise state değişkenine ilk değer ataması yapılmakta.
  • Ve gelelim MoveNext metoduna. Gördüğünüz gibi MoveNext metodu kocaman bir switch-case ifadesi. Şimdilik bu ifadeye çok detaya girmeden yüksek seviye bir bakış yaparsak temel olarak içerisinde bulunan durumlara göre çeşitli işlemler yaptığını görüyoruz.
  • IEnumerator.Reset metodu NotSupportedException döndürmekte.
  • Son olarak ise IEnumerator ve IEnumerator<object> interfacelerinden gelen Current propertyleri class içerisinde tanımlı olan mevcut elemanı döndürmekte.

Genel olarak yapıyı incelediğimizde compiler’ın aslında arka planda bir state – machine(durum makinası) yarattığını görmüş oluyoruz. İçerisinde bulunduğu mevcut durumlara göre akış çeşitli yerlere dallanmakta ve ilgili eleman current propertysine atanmakta. Şimdi gelin örneğimizi bu sefer IEnumerator’ın generic versiyonuna göre güncelleyelim. Buna göre iterator metodumuzun imzası şu şekilde olacak.

static IEnumerator<string> GetDays()
 

Şimdi kodumuzu yeniden derleyelim ve reflector ile tekrardan açalım. Bakalım ne gibi değişiklilkler oldu.

private sealed class <GetDays>d__0 : IEnumerator<string>, IEnumerator, IDisposable

İlk değişiklik compiler tarafından yaratılan inner-class’ın imzasında meydana geldi. Generic versiyonu kullanmadığımız durumda class’ın implemente ettiği IEnumerator interface’inin generic parametresi object iken bu kez string oldu.

Bunun yanında oluşan bir diğer değişiklik ise inner-class içerisinde mevcut elemanı tutan current değişkeninde ve IEnumerator interface’i tarafından gelen Current propertysinde.

private string <>2__current; string IEnumerator<string>.Current { [DebuggerHidden] get { return this.<>2__current; } }

Gördüğünüz gibi 2 alanında tipleri object’ten string’e döndü.

Şimdi sırada aynı örneğin IEnumerable<string> döndüren versiyonu var. Metodumuzun imzasını şu şekilde değiştiriyoruz.

internal class Program { // Methods private static IEnumerable<string> GetDays() { return new <GetDays>d__0(-2); } // Nested Types [CompilerGenerated] private sealed class <GetDays>d__0 : IEnumerable<string>, IEnumerable, IEnumerator<string>, IEnumerator, IDisposable { // Fields private bool $__disposing; private int <>1__state; private string <>2__current; private int <>l__initialThreadId; // Methods [DebuggerHidden] public <GetDays>d__0(int <>1__state) { this.<>1__state = <>1__state; this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId; } private bool MoveNext() { try { bool $__doFinallyBodies = true; switch (this.<>1__state) { case 1: if (this.$__disposing) { return false; } this.<>1__state = 0; this.<>2__current = "Salı"; this.<>1__state = 2; $__doFinallyBodies = false; return true; case 2: if (this.$__disposing) { return false; } this.<>1__state = 0; this.<>2__current = "\x00c7arşamba"; this.<>1__state = 3; $__doFinallyBodies = false; return true; case 3: if (this.$__disposing) { return false; } this.<>1__state = 0; Console.WriteLine("Hafta ortası"); this.<>2__current = "Perşembe"; this.<>1__state = 4; $__doFinallyBodies = false; return true; case 4: if (this.$__disposing) { return false; } this.<>1__state = 0; this.<>2__current = "Cuma"; this.<>1__state = 5; $__doFinallyBodies = false; return true; case 5: if (this.$__disposing) { return false; } this.<>1__state = 0; this.<>2__current = "Cumartesi"; this.<>1__state = 6; $__doFinallyBodies = false; return true; case 6: if (this.$__disposing) { return false; } this.<>1__state = 0; this.<>2__current = "Pazar"; this.<>1__state = 7; $__doFinallyBodies = false; return true; case 7: break; case -1: return false; default: if (this.$__disposing) { return false; } Console.WriteLine("Hafta başlıyor"); this.<>2__current = "Pazartesi"; this.<>1__state = 1; $__doFinallyBodies = false; return true; } if (this.$__disposing) { return false; } this.<>1__state = 0; Console.WriteLine("Hafta sonlanıyor..."); } catch (Exception) { this.<>1__state = -1; throw; } this.<>1__state = -1; return false; } [DebuggerHidden] IEnumerator<string> IEnumerable<string>.GetEnumerator() { if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2)) { this.<>1__state = 0; return this; } return new Program.<GetDays>d__0(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return this.System.Collections.Generic.IEnumerable<System.String>.GetEnumerator(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] void IDisposable.Dispose() { this.$__disposing = true; this.MoveNext(); this.<>1__state = -1; } // Properties string IEnumerator<string>.Current { [DebuggerHidden] get { return this.<>2__current; } }

        object IEnumerator.Current { [DebuggerHidden] get { return this.<>2__current; } } } }

Iterator metodumuzun dönüş tipini IEnumerable<string> yapınca compiler tarafında yapılan değişikliklerin sayısı biraz arttı.

Şimdi bu değişiklikleri sıralayalım.

  • İlk olarak farkettiyseniz yazmış olduğumuz iterator metot içerisinden yarattılan inner-class örneğinin constructor’ına artık 0 değil –2 parametresi gidiyor. Yani ilk durumda state değişkeni –2’ye çekiliyor.
  • Compiler tarafından üretilen inner-class içerisinde iterator’ı yaratan thread’in Id’sini tutmak üzere initialThreadId isimli bir değişken eklenmiştir.

Sonuç olarak baktığımızda compiler yarattığımız iterator bloklarını arka planda tamamen yeniden yazıp bir state-machine haline getiriyor ve foreach ifadeleri üzerinde yapılan çağrıların bu state-machine içerisinde çalışması sağlanıyor. Şimdi gelin compiler tarafından yaratılan state machine mekanizmasının derinlerine dalalım ve iterator’a gelen istekler nasıl değerlendiriliyor ve state-machine içerisinde durumlar arası nasıl geçiş yapılıyor inceleyelim.

Compiler Tarafından Yaratılan State-Machine

Yukarıdaki paragraflardaki incelemelerimizde compiler tarafından yaratılan inner-class içerisinde bir state-machine implementasyonu yapıldığını gördük. Bu yaratılan state-machine iterator’a yapılan tüm çağrılarda kullanılmakta ve işletilmekte. İşletilen durumlara göre de makinanın durumu değişmekte ve değişen duruma göre de farklı bir akış işletilmekte. Şimdi en baştan başlayarak state-machine içerisinde yapılan işlemleri ve mevcut durumlarını inceleyelim.

İlk olarak iterator’a ilk istekte bulunulduğunda öncelikli olarak bizim mevcutta yazmış olduğumuz GetDays isimli metot çağrılmakta ve bir IEnumerable<string> nesne örneği talep edilmekte. Bu metot içerisinde de compiler tarafından yaratılmış olan sınıfın örneklenmesi yapılmakta ve constructor içerisinde –2 değeri parametre olarak geçirilmekte. Bu nedenle iterator’ın ilk yaratıldığında bulunduğu durum –2 durumudur. Ayrıca yine constructor içerisinde iterator’ı yaratan thread’in Id değeri de local değişkene atanarak bu değer ileride kullanılmak üzere saklanmakta.

Sonrasında ise  foreach döngüsü tarafından iterator içerisindeki GetEnumerator metodu çağrılmakta. Bu metot içerisinde ise eğer çağrıyı yapan thread’in Id değeri mevcut iterator’ı yaratan yani yukarıdaki paragrafta yaratma görevini yapan thread ile aynı ise state –machine ‘in durumu 0 durumuna çekilmekte ve mevcut sınıf foreach döngüsüne dönülmekte. Eğer bu durum sağlanmıyor ise mevcut sınıfın bir örneği yaratılarak yeni yaratılan state-machine’in durumu 0’a getiriliyor. Böylece de farklı threadler üzerinden kullanılan iterator’ların her birinin değerlerinin ve durumlarının saklanması bu şekilde mümkün olabilmekte.

Bu noktadan sonra ise artık foreach tarafından Enumerator üzerinden MoveNext metodu çağrıları gerçekleşmekte. Bu çağrılar sırasında ilk durumumuz 0 durumuydu. Bu durumda switch-case ifadesinde doğrudan default etiketi işletilecektir. Bu durumda ilgili değer current değişkenine atandıktan sonra state-machine’in durumu 1’e getirilmekte.

Sonrasında ise foreach döngüsü tarafından yapılan diğer MoveNext çağrılarında ise yine compiler tarafından yazılmış olan case ifadeleri içerisinde öncelikle makinanın durumu 0’a çekilmekte. Akabinde ise sıradaki eleman current değişkenine aktarılırken sonrasında ise durumun değeri bir sonraki case ifadesinin değerine geçiriliyor. state değişkeninin değeri 7 oluncaya kadar bu paragrafta bahsetmiş olduğumuz akış işletilmekte.

State değeri 7 olduğunda ise gördüğünüz gibi sadece break ifadesi bulunmakta. Böylece aslında case içerisinde hiç bir işlem yapılmadan çıkılmakta. Sonrasında ise son olarak yazdığımız “Hafta sonlanıyor” ifadesini yazımı için state değişkeni 0’a çekilerek ilgili kod işlendikten sonra makinanın durumu son olarak –1’e çekiliyor ve bu sefer MoveNext metodundan false değer geriye döndürülüyor. Makinanın durumunun –1’e çekilmesiyle durum makinasının artık sona geldiğini görmekteyiz.

Ayrıca MoveNext metodu içerisinde try-catch bloğunun da yaratıldığını görmekteyiz. Böylece try bloğu içerisinde bir exception olduğu durumda durum makinasının durumu otomatik olarak –1’e çekilerek makinanın son durumuna durumuna dönmesi sağlanmakta.

Şimdi isterseniz durum makinası içerisindeki durumların değerlerini kısaca özetleyelim.

-2 : Bu durum IEnumerable örneğinin yaratıldığı ilk durumdur. Bu durumda henüz IEnumerator içerisinde GetEnumerator isimli metot çağrılmamıştır.

0 : Bu durum içerisinde GetEnumerator metodu çağrılarak ilgili Enumerator foreach ifadesine dönülmüştür. Bu durumda MoveNext metodunda switch-case ifadesinin default etiketi işletilir.

1-7 : Bu durumlar yazmış olduğumuz yield ifadelerinin akışını temsil eder. İlgili değerler current değişkenine atandıktan sonra durum değerleri 1 arttırılır.

-1 : Bu durumda iterator’ın sonuna gelinmiştir ve daha erişilecek eleman kalmamıştır.

Şimdi isterseniz yazımızın başında incelediğimiz diğer örneğimiz olan bir array içerisindeki ilk iki sıradaki çift sayıyı döndüren iterator metodunun arka planını inceleyelim.

internal class Program { // Fields private static int[] numbers = null; private static IEnumerable<int> GetTwoEvenNumbers() { return new <GetTwoEvenNumbers>d__0(-2); } private static void Main(string[] args) { FillArray(); foreach (int number in GetTwoEvenNumbers()) { Console.WriteLine(number); } } // Nested Types [CompilerGenerated] private sealed class <GetTwoEvenNumbers>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable { // Fields private bool $__disposing; private int <>1__state; private int <>2__current; private int <>l__initialThreadId; public int <evenNumber>5__1; public int <i>5__2; // Methods [DebuggerHidden] public <GetTwoEvenNumbers>d__0(int <>1__state) { this.<>1__state = <>1__state; this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId; } private bool MoveNext() { try { bool $__doFinallyBodies = true; if (this.<>1__state == 1) { goto Label_00B9; } if (this.<>1__state == -1) { return false; } if (this.$__disposing) { return false; } this.<evenNumber>5__1 = 0; this.<i>5__2 = 0; while (this.<i>5__2 < Program.numbers.Length) { if (this.<evenNumber>5__1 == 2) { goto Label_0108; } if ((Program.numbers[this.<i>5__2] % 2) != 0) { goto Label_00D4; } this.<evenNumber>5__1++; this.<>2__current = Program.numbers[this.<i>5__2]; this.<>1__state = 1; $__doFinallyBodies = false; return true; Label_00B9: if (this.$__disposing) { return false; } this.<>1__state = 0; Label_00D4: this.<i>5__2++; } } catch (Exception) { this.<>1__state = -1; throw; } Label_0108: this.<>1__state = -1; return false; } [DebuggerHidden] IEnumerator<int> IEnumerable<int>.GetEnumerator() { if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2)) { this.<>1__state = 0; return this; } return new Program.<GetTwoEvenNumbers>d__0(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] void IDisposable.Dispose() { this.$__disposing = true; this.MoveNext(); this.<>1__state = -1; } // Properties int IEnumerator<int>.Current { [DebuggerHidden] get { return this.<>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return this.<>2__current; } } } }

Sanki compiler’ın yeniden yazım şekli biraz değişti gibi :) Bunun nedeni ise aslında durum makinasının akışının çok belirgin olmaması. Çünkü ilk örneğimizde sırayla yield ifadeleri kullanırken bu sefer belirli koşullara uygun olarak iterasyonumuzu devam ettirmekteyiz ve bazı koşullara göre de iterasyonu sonlandırmaktayız.

Ayrıca compiler tarafından yazılan kodlara baktığımızda goto ifadelerine rastlamaktayız. Pratikte biz geliştiricilerin goto kullanması çokta tavsiye edilmezken compiler tarafından yazılan kodlara baktığımızda sıklıkla goto ifadelerini görürüz. Çünkü goto ifadeleri ile compiler optimizasyonu yapmak ve bir takım kodların akışının kontrol edilmesi oldukça kolaylaşmakta.

Bunların yanında compiler tarafından yaratılan sınıfın neden bir inner-class olduğunu merak ettiğinizi düşünüyorum. :) Bunun nedeni de aslında oldukça basit. Bulunduğu dış sınıf içerisinde bulunan private değişkenler dahil tüm değişkenlere erişebiliyor olabilmesi.

MoveNext metodunun yapısına baktığımızda aslında bu sefer switch-case yapısının olmadığını görüyoruz. Bunun yerine bir while döngüsü ile array içerisindeki elemanlar içerisinde iterasyon yapılmakta. While döngüsü içerisinde öncelikle iterasyondan döndürülen çift sayıların sayısı kontrol edilmekte. Eğer bu değer 2’ye eşit ise goto ifadesi ile ilgili label’a atlanılarak buradan durum makinasının durumunun –1’e çekilmesi sağlanmakta. Eğer sayı 2’den farklı ise sayının 2’ye tam bölünüp bölünmediği kontrol edilmekte. Eğer sayı tam bölünemiyor ise while döngüsü içerisindeki index değeri 1 arttırılıyor. Eğer sayı tam bölünebilen bir sayı ise de bu değer öncelikle current değişkenine atanıyor ve makinanın durumu 1’e çekiliyor.

Makinanın durumunun 1’e çekilmesi aslında makinanın işletimde olduğunu göstermekte. Böylece MoveNext metodu her çağrıldığında öncelikli olarak akış while döngüsü içerisinde bulunan bir label’a yönlenmekte. Sonrasında ise bu label içerisinde makinanın durumu 0'a çekilmekte ve while döngüsündeki kontroller baştan işletilmekte.

Bitirirken

Evet arkadaşlar bu yazımızda aslında C# içerisinde uzun süredir bulunan ancak pek fazla bilinmeyen ancak oldukça faydalı ve sihirli bir keyword olan yield keyword’ünü inceledik. Aslında baktığımızda 10 satır gibi kısa bir kod parçasıyla işimizi hallederken aslında compiler’ın arka planda nasıl bir görevi üstlendiğini görmüş olduk.

Eğer önceki yazılarımda daha önce denk geldiyseniz zaman zaman compilerlara olan hayranlığımdan bahsetmişimdir. Çünkü arka planda gerçekten mükemmel işler yapıyorlar. Bu nedenle bence özel bir takdir’i hak ediyorlar. Bunun yanında biz developerlarında aslında bu detaylara biraz aşina olmasında yarar var. Çünkü ancak bu şekilde en doğru kullanımları gerçekleştirebiliriz.

İlerleyen yazılarda zaman zaman sizlerle yine C# compilerı tarafında arka planda ne gibi işler döndüğünü paylaşmaya çalışacağım. :)

Bu arada bu yazımı okuyan VB developerlar VB’de Iterator’ların olup olmadığını merak ediyor olabilirler. Iterator’lar C# ve VB’nin özelliklerinin eşlenmesi kapsamı içerisinde VB’nin bir sonraki versiyonu olan VB.NET 11 içerisinde geliyor olacak. VB için detaylı bilgiyi buradan elde edebilirsiniz.

Hoşçakalın



Yorum Gönder


Yorumlar

  • profile

    Ceren Avci

    • 10
    • 7
    • 2017

    Oldukça faydalı bir makale olmuş. Emeğinize sağlık.

  • profile

    Gurcag .

    • 14
    • 3
    • 2017

    Emeğinize sağlık çok yararlı ve ufuk açıcı bir yazı.

  • profile

    ismail şimşek

    • 16
    • 7
    • 2016

    çok güzel bir anlatım olmuş.yield nedir diye araştırma yapmıştım ancak böyle detaylısına rastlamamıştım. teşekkürler.

  • profile

    Burak Doğan

    • 7
    • 6
    • 2016

    İyi anlatım. Teşekkürler.

  • profile

    erhan .

    • 22
    • 9
    • 2015

    Anlatım çok güzel ve sade olmuş.İyileştirme olarak reflector aracılığıyla bakılan kodları resim olarak koysaydın daha iyi olurdu. Tşk.

  • profile

    Cüneyt Atalay Yardım

    • 1
    • 9
    • 2015

    Beynim sulandı. Arşivlik bir makale. Teşekkürler .

  • profile

    fuat tatar

    • 25
    • 9
    • 2012

    Teşekkürler çok faydalı bir yazı.

  • profile

    kenan

    • 5
    • 2
    • 2012

    Yield kavramını güzel anlatmışsın reis...Eyw.