IEnumerator etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
IEnumerator etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

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

C# IEnumerable, IEnumerator Ara yüzleri ve Kullanımı

C#'ta iterator kavramını düşündüğümüzde tahmin ediyorum ki aklınıza gelen ilk yapı foreach yapısıdır. foreach ile bir koleksiyon ya da array üzerinde gezinerek elemanlarına ulaşabiliyoruz. foreach arkaplanda iterator yapısını kullanır.

Bugün inceliyeceğimiz konu biz yazdığımız bir sınıf içerisindeki elemanları foreach ile gezmek istesek ne yapmamız gerekiyor? Örneğin AlisverisSepeti diye bir sınıf oluşturduk bu sınıfın içerisinde Urun nesneleri var bu nesneleri foreach ile itere etmek istiyoruz ne yapmamız gerekiyor?

İlk aklımıza gelen şekilde programımızı bir yazalım:

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

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

        public AlisverisSepeti()
        {
            UrunListesi = new Urun[] 
            { 
                new Urun(){ Name="Bilgisayar", Price=1022.3m},
                new Urun(){ Name="Ipad", Price=699.9m}
            };
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            AlisverisSepeti sepetim = new AlisverisSepeti();

            foreach (Urun urun in sepetim)
            {
                Console.WriteLine("Urun: {0}, Fiyat: {1}"
                       , urun.Name, urun.Price); 
            }
        }
    }


Gördüğünüz gibi bir AlisverisSepeti sınıfı oluşturduk ve bu sınıf içerisindeki Urun listesine bir takım ürünler attık. Daha sonra ise alisveris sepetimizi foreach ile gezmeye çalıştık ama bu durumda bu mümkün değil. Böyle bir tasarım yaparsak aşağıdaki hatayı alırız:

foreach statement cannot operate on variables of type 'IteratorsExample.AlisverisSepeti' because 'IteratorsExample.AlisverisSepeti' does not contain a public definition for 'GetEnumerator'

Bu sınıfımızın iterator özelliği kazanması 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. Bir sınıf üzerinde foreach ile dönebilmemiz için bu sınıfın GetEnumerator metodunun olması gerekmektedir.

Şimdi AlisverisSepeti sınıfımızı IEnumerable interface'ini implemente edecek şekilde değiştirelim:

    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()
        {
            throw new NotImplementedException();
        }
    }

Gördüğünüz gibi IEnumerable interface'ini Alisveris sınıfımıza implemente ettiğimizde IDE'miz bize IEnumerator döndüren GetEnumerator isimli bir metot yazmamış gerektiğini söyledi. Örneğimizde bu metodu yazabilmemiz için IEnumerator interface'ini implemente etmiş AlisverisSepetiEnumerator sınıfını yazmamız gerekecek. IEnumerator interface'i ise uygulandığı sınıfa MoveNext, Reset adlı bir metotları ve readonly Current property'sini kazandırır.

    class AlisverisSepetiEnumerator:IEnumerator
    {
        public object Current
        {
            get { throw new NotImplementedException(); }
        }

        public bool MoveNext()
        {
            throw new NotImplementedException();
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }
    }

 Şimdi bu implementasyonumuzu gerçekleştirelim:

    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;
        }
    }

AlisverisSepeti sınıfımız için custum bir Enumerator yazdığımıza göre artık GetEnumerator metodumuzu düzenleyebiliriz:

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

Bu son değişikliğin ardında artık AlisverisSepeti sınıfımız itere edilebilir hale geldi programımızın çıktısı şu şekilde olacak:


Bu programda kullandığımız sepetimizdeki urunleri tutan Urun[] collection'ı itere edilebilir bir yapıda olduğu için kendi içerisinde tanımlanmış GetEnumerator metodu bulunmaktadır. Bu yüzden manuel olarak Enumerator sınıfı yazmak yerine UrunListesi.GetEnumerator() şeklinde çağırarak bu iterasyon işlemini gerçekleştirebilirdik. Yukarıdaki örneğimizi bu metodu olmayan nesneleri manuel nasıl itere edebileceğimizi göstermek için verdik. Daha kolay bir şekilde şöyle gerçekleştirebilirdik:

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

IEnumerator interface'inin birde generic hali IEnumerator<T> bulunmakta bunu ilerleyen yazılarımızda inceleyeceğiz.

Örnek Proje: IEnumeratorExample.rar