C# Yield Sözcüğü Kullanımı

Başlamadan daha önce okumadıysanız C# IEnumerable, IEnumerator Ara yüzleri ve Kullanımı yazısını okumanızı tavsiye ederim. Çünkü bu yazıda bir çok yerde bu yazıdan referans alacağız.

C# IEnumerable, IEnumerator Ara yüzleri ve Kullanımı yazısında bahsettiğimiz gibi bir sınıfımızın iterator özelliği kazanabilmesi için IEnumerable veya IEnumerable<T> interfacelerini implemente etmesi gerekmektedir.

Bu interface, uygulandığı sınıfa GetEnumerator adlı bir metot kazandırır ve bu metot geriye IEnumerator interface’ini implement eden bir sınıf döndürür.

Yine aynı yazıda custom bir Enumerator sınıfı yazıp, IEnumerator interface'ini implemente etmiştik. Bu implementasyonun ardından MoveNext, Reset adlı metotları ve readonly Current property'sini doldurmamız gerekmişti.

Bunları yapmak oldukça basit olsada bazi durumlarda, uygulamaya entegrasyonu sorunlu olabiliyor veya zaman alabiliyor her seferinde bunları yazmak. Bu tip sıkıntıları gidermek için C# 2.0 ile yield keyword'ü gelmiştir. Yield ile bu işlemlerin tümü bizim için arka planda yapılıyor.

Yield keyword’ünün iki farklı kullanım şekli bulunuyor:
yield return <ifade>
yield break
yield return <ifade> : Iterator’e çağrı yapan foreach döngüsüne bir eleman döndürülürken kullanılır.
yield break: Mevcut iterator içerisindeki iterasyonun sona erdiği bilgisini iterator’ı çağıran foreach döngüsüne iletmekte kullanılır.

Şimdi bu ifadelerin daha açık olması için bir örnek üzerinden gidelim ve bir önceki yazımızda custom Enumerator sınıfı ile yazdığımız Alisveris Sepeti iterator örneği hatırlayalım:

    class Urun
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }

    class AlisverisSepeti:IEnumerable
    {
        public Urun[] UrunListesi { get; set; }

        public AlisverisSepeti()
        {
            UrunListesi = new Urun[] 
            { 
                new Urun(){ Name="Bilgisayar", Price=1022.3m},
                new Urun(){ Name="Ipad", Price=699.9m}
            };
        }

        public IEnumerator GetEnumerator()
        {
            //return UrunListesi.GetEnumerator();
            return new AlisverisSepetiEnumerator(UrunListesi);
        }
    }

class AlisverisSepetiEnumerator:IEnumerator
    {
        private Urun[] _UrunList;
        int _curentIndex = -1;

        //itere edilecek liste aliniyor
        public AlisverisSepetiEnumerator(Urun[] urunList)
        {
            _UrunList = urunList;
        }

        //iterasyonda kalinan yer
        public object Current
        {
            get { return _UrunList[_curentIndex]; }
        }

        //bir sonraki adim var mi kontrolu
        public bool MoveNext()
        {
            return ++_curentIndex < _UrunList.Length;
        }

        //iterasyon basa alinir
        public void Reset()
        {
            _curentIndex = -1;
        }
    }

Gördüğünüz gibi AlisverisSepetiEnumerator sınıfı oluşturup IEnumerator interface'ini implemente etmiştik. Peki bunu yield ile nası yazarız? Artık custum olarak yazdığımız enumerator sınıfı AlisverisSepetiEnumerator'a gerek yok tek yapmamız gereken:

        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < UrunListesi.Length; i++)
            {
                yield return UrunListesi[i];
            }
        }
Normalde bilirsiniz ki bir metod birden fazla return yapamaz fakat return'ün başına yield koyduğumuzda ne oluyorda yapabiliyor? Compiler yield keyword'ünü gördüğünde bunun bir iterator bloğu olduğunu anlıyor ve bizim yerimize MoveNext,Reset ve Current öğelerini yaratıp içlerini dolduruyor.

Yani artık custom bir enumerator sınıfı yazmamıza gerek yok yield keyword'ü ile compiler bunu arka planda bizim için yapıyor.

ildasm.exe ile programımızın exe dosyasını açarsak bunu açıkça görebiliriz:



Yield ile kendimiz named iteretor'ler oluşturabiliriz. Örneğin programımızın 750TL den daha pahalı ürünleri getirmesini istiyoruz:

        public IEnumerable GetHighPriceList()
        {
            for (int i = 0; i < UrunListesi.Length; i++)
            {
                if(UrunListesi[i].Price > 750m)
                    yield return UrunListesi[i];
            }
        }

foreach içerisinde yukardaki örnekte foreach(Urun item in sepetim) şeklinde yazmıştık. Bu durumda sepetim nesnesinin içerisindeki elemanları çekmek için foreach GetEnumerator metodunu çağırmaktadır. Yukarda aslında kendi GetEnumerator sınıfımızı yazmış olduk artık sepetim.GetHighPriceList() şeklinde foreach içerisine yazarsak artık GetEnumerator metodu yerine GetHighPriceList metodu çağırılacaktır ve bu nedenle iterasyon artık bu metodun döndürdüğü elemanlar içerisinde olacaktır.

             // default GetEnumerator yerine
             // GetHighPriceList metodu calistirilacak
             foreach (Urun urun in sepetim.GetHighPriceList())
             {
                 Console.WriteLine("Urun: {0}, Fiyat: {1}"
                     , urun.Name, urun.Price);
             }

Programımızın çıktısı şu şekilde olacaktır:


Örnek program: IEnumerableWithYield.rar

2 yorum :