5 Eylül 2011 Pazartesi

C# Foreach İterasyonu

İterasyon, belli bir kurala dayalı işlem yapmaktır. Bu yazımızda foreach iterasyonunun hangi kurallara göre yapıldığını ve arka planda ne tarz işlemlerin gerçekleştiğini incelemeye çalışacağız. Hepimizin bildiği gibi foreach döngüsü, bir koleksiyon içerisinde ilk elemandan başlanarak son elemana kadar sıra ile ilerleyen bir yapıya sahiptir. İlk olarak basit bir int dizisi oluşturup içerisinde foreach ile gezerek, dizi içerisindeki sayıların karesini ekrana yazdıralım.

class Program

    {

        static void Main(string[] args)

        {

            int[] sayilar = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

 

            foreach (int sayi in sayilar)

            {

                Console.WriteLine(sayi * sayi);

            }

        }

    }

“sayilar” adlı dizinin içerisinde int tipinden nesneler vardır. Döngümüzü yazarken de bunu göz önünüde bulundurduk. sayilar içerisinden gelen her bir sayının karesini alarak ekrana yazdırdık. Döngümüz 1’den başlayarak sırayla “sayilar” dizisinin içinde gezerek ekrana yazdırma işlemini gerçekleştirmektedir.

Bu döngü sırasında son elemandan başlayıp geriye gitmek, elemanlar arasında ikişer ikişer atlayarak işlem yapmak vb. durumları ele alamayız. Peki neden? Girişte de bahsettiğimiz gibi her iterasyon belirli kurallara dayalıdır. foreach iterasyonu sırasında bu kuralların belirlendiği yapılardan bahsetmemiz mümkün. Kural kelimesini duyunca aklımıza interface’ler gelebilir. Çünkü interface’lerin temel kullanım amaçlarından biri, uygulandığı tipe bazı kurallar getirmesidir. Oluşturduğumuz nesne örnekleri üzerinde foreach iterasyonu uygulayabilmemiz için, o tipin IEnumerable interface’ini uygulaması gerekmektedir. Şimdi bunu görmeye çalışalım. Bildiğimiz gibi, oluşturduğumuz int dizisi çalışma zamanında bir Array muamelesi görmektedir. Şimdi bunu ispatlayalım.

Bunu anlamak için reflection yapabiliriz.

int[] sayilar = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

Console.WriteLine(sayilar.GetType().BaseType);

Yukarıdaki kodu çalıştırarak ekran çıktısını inceleyelim.

Untitled

Görüldüğü gibi, oluşturduğumuz int dizisi abstract olan Array sınıfından kalıtılmıştır. Şimdi de Array sınıfına Go To Definition diyelim.

Untitled

Yukarıda da bahsettiğimiz gibi bir tip içerisinde foreach iterasyonunun yapılabilmesi, o tipe IEnumerable interface’inin uygulanması gerekmektedir.

Untitled

Böylece oluşturduğumuz integer dizisi üzerinde foreach iterasyonu yapabilmemizi sağlayan interface’I görmüş olduk. Şimdi de kendi yazdığımız bir tip üzerinde iterasyon yapmayı deneyelim. Urun ve Dukkan adlı iki sınıf oluşturalım ve bu sınıflara ait property’ler ile contructor metotları tanımlayalım.

class Urun

    {

        private int _id;

        private string _ad;

        private double _fiyat;

 

        public int Id

        {

            get { return _id; }

            set { _id = value; }

        }

 

        public string Ad

        {

            get { return _ad; }

            set { _ad = value; }

        }

 

        public double Fiyat

        {

            get { return _fiyat; }

            set { _fiyat = value; }

        }

 

        public Urun(int id, string ad, double fiyat)

        {

            Id = id;

            Ad = ad;

            Fiyat = fiyat;

        }

 

    }

class Dukkan

    {

        private Urun[] _urunSepeti;

 

        public Urun[] UrunSepeti

        {

            get { return _urunSepeti; }

            set { _urunSepeti = value; }

        }

 

        private Urun[] UrunleriGetir()

        {

            return new Urun[]

            {

                new Urun(1, "Monitor", 200),

                new Urun(2, "Klavye", 80),

                new Urun(3, "Mouse", 40)

            };

 

        }

 

        public Dukkan()

        {

            UrunSepeti = UrunleriGetir();

        }

    }

Dukkan adlı sınıfın constructor metodunda UrunleriGetir() metodunu çağırarak UrunSepeti içerisine doldurduk. Şimdi de Dukkan tipinden bir nesne örnekleyelim ve foreach ile gezmeye çalışalım.

class Program

    {

        static void Main(string[] args)

        {

            Dukkan urunler = new Dukkan();

 

            foreach (Urun urn in urunler)

            {

                Console.WriteLine(urn.Ad);

            }

        }

    }

Bu işlemden sonra programı derlediğimizde aşağıdaki gibi bir hata alırız.

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

Hatanın sebebine gelirsek; içerisinde gezmeye çalıştığımız nesne örneği Dukkan tipindendir. Bu tipten bir nesne örneği üzerinde iterasyon yapabilmemiz için, Dukkan sınıfının IEnumerable interface’ini implement etmesi gerekir. Bu interface, uygulandığı tipe GetEnumerator adlı bir metot tanımlası yapma zorunluluğu getirir. Bizim yazmış olduğumuz sınıfta böyle bir metot olmadığından ötürü hata aldık. Çünkü foreach iterasyonu yapmak demek, nesne örneği hangi tipteyse, o tipin GetEnumerator metodunu çağırmak anlamına gelir.

Şimdi de Dukkan tipinden bir nesne örneği üzerinde foreach ile gezilebilmesi için gerekli olan IEnumerable interface’ini implement edelim. Bu interface ile birlikte, GetEnumerator() adlı bir metota gövde kazandırmamız gerekecek.

class Dukkan : IEnumerable

    {

        private Urun[] _urunSepeti;

 

        public Urun[] UrunSepeti

        {

            get { return _urunSepeti; }

            set { _urunSepeti = value; }

        }

 

        private Urun[] UrunleriGetir()

        {

            return new Urun[]

            {

                new Urun(1, "Monitor", 200),

                new Urun(2, "Klavye", 80),

                new Urun(3, "Mouse", 40)

            };

 

        }

 

        public Dukkan()

        {

            UrunSepeti = UrunleriGetir();

        }

       

        public IEnumerator GetEnumerator()

        {

            throw new NotImplementedException();

        }

    }

GetEnumerator() metodu geriye IEnumerator interface’ini implement eden bir tip döndürür. IEnumerator, iterasyonun kurallarını belirleyen interface’dir. Buradaki seneryomuz, urunler içerisindeki her bir Urun’de gezmek olsun. Bunun için IEnumerator interface’ini implement eden UrunNumerator adlı bir sınıf yazalım.

class UrunNumerator : IEnumerator

    {

        public object Current

        {

            get { throw new NotImplementedException(); }

        }

 

        public bool MoveNext()

        {

            throw new NotImplementedException();

        }

 

        public void Reset()

        {

            throw new NotImplementedException();

        }

    }

Bu interface ile birlikte gövde kazandırmamız gereken 3 üyeye sahip olduk. UrunNumerator sınıfı, iterasyonu yapacak olan sınıftır. Bu 3 üye de iterasyonun kurallarını berlirlememizi sağlar.

Yapmamız gereken son işlem, IEnumerable ve IEnumerator interface’I ile gelen abstract üyelere gövde kazandırmaktır. İlk olarak UrunNumerator adlı sınıfın kodlarını aşağıdaki hale getirelim.

class UrunNumerator : IEnumerator

    {

        private Urun[] _IterasyonYapilacakUrunler;

        int _sayac;

 

        public UrunNumerator(Urun[] urunler)

        {

            _IterasyonYapilacakUrunler = urunler;

            Reset();

        }

 

        public object Current

        {

            get { return _IterasyonYapilacakUrunler[_sayac]; }

        }

 

        public bool MoveNext()

        {

            return ++_sayac < _IterasyonYapilacakUrunler.Length;

        }

 

        public void Reset()

        {

            _sayac = -1;

        }

    }

Şimdi de Dukkan sınıfı içerisinde yer alan GetEnumerator() metodunun içini aşağıdaki gibi değiştirelim.

public IEnumerator GetEnumerator()

    {

        return new UrunNumerator(UrunSepeti);

    }

GetEnumerator metodu içerisinde UrunNumerator nesne örneği oluşturduk ve constructor metoduna parametre olarak UrunSepetini verdik. Current property’si o anki sayac indexli elemanı okumamızı sağlarken, MoveNext metodu da okuma yapacak eleman olup olmadığını kontrol eder. Reset metodu sayac’a –1 değerini atar.

Şimdi de Program tarafında Dukkan tipinden bir nesne örnekleyelim ve içerisinde foreach ile gezmeye çalışalım.

class Program

    {

        static void Main(string[] args)

        {

            Dukkan urunler = new Dukkan();

 

            foreach (Urun urn in urunler)

            {

                Console.WriteLine(urn.Ad);

            }         

        }

    }

Burada dikkat edilmesi gereken nokta urunlerden geriye “Urun” tipinin dönmesidir. Bunun nedeni ise, Current property’si içerisinde get ile döndürülen değer, o anki Urun nesnesinin kendisidir, Yani Urun tipinde bir geri dönüş beklememiz normaldir. Aslında Current property’si geriye object döndürmektedir. Bu sebepten ötürü foreach iterasyonu yaptığımızda, içerisinde gezdiğimiz koleksiyondan geriye object dönecektir. Ancak burada urunler içerisinde Urun tipinden nesneler olduğunu bildiğimiz için, Urun tipinden geri dönüş olacağını yazabiliriz. Alacağımız ekran görüntüsü aşağıdaki gibi olacaktır.

Untitled

UrunSepetinde bulunan 3 Urunun adlarını elde etmiş olduk. foreach, bizim için kolay bir syntax görevi görür aslında. Arka planda yapılan iş, içerisinde gezilecek olan tipin GetEnumerator metodunun çağırılmasıdır. Daha sonra da IEnumerator interface’inin üyeleri çağırılarak iterasyon yaptırılır. Aşağıdaki kod, yukarıda yazdığımız foreach ifadesi ile aynıdır. Dolayısı ile aynı ekran görüntüsünü verecektir.

class Program

    {

        static void Main(string[] args)

        {

            Dukkan urunler = new Dukkan();

 

            UrunNumerator e = urunler.GetEnumerator() as UrunNumerator;

            //IEnumerator e = urunler.GetEnumerator(); yukarıdaki ile aynı işlevi görür.

           

            while (e.MoveNext())

            {

                Urun urn = (Urun)e.Current; /*/

                Console.WriteLine(urn.Ad);

            }

        }

    }

/*/ –> Current Property’si geriye object döndürdüğü için Urun tipine cast ettik.

Son olarak yapılan işlemleri birer cümle ile özetlemek gerekirse;

1-İçinde foreach ile gezilmek istenen nesnenin GetEnumerator ismindeki metodu çalıştırılır
2-GetEnumerator metodundan geriye IEnumerator implement eden bir nesne döner.
3-Gelen bu nesnenin Current, MoveNext ve Reset üyeleri olmak zorundadır.
4-Foreach iterasyonu sırasında bir sonraki elemana hareket etme işi, MoveNext() metodu aracılığıyla gerçekleştirilir.
5-Iterasyon sırasında o anda bulunan eleman Current property'si ile okunur.
6-Foreach iterasyonu, elemanlar bitince sonlanır. Çünkü MoveNext metodu false döner.

Faydalı olması dileği ile…



6 yorum:

Sadık Bozkurt dedi ki...

Güzel ve açıklayıcı bir yazı olmuş teşekkürler

Abdurrahman Güngör dedi ki...

Güzel bir yazı olmuş teşekkürler

Hur_Fatih dedi ki...

Gayet anlaşılır. Emek için teşekkürler.

Adsız dedi ki...

Eyw Üstadım..Emeğine sağlık..Allah Razı Olsun..

Adsız dedi ki...

Merhaba, Dukkan clasının içindekilerini sildim public List Sepet { get; set; } oluşturdum. maindende ,
d.Sepet = new List()
{
new Urun() { Id = 1, Ad = "Elma", Fiyat = 2.3 },
new Urun() { Id = 2, Ad = "Portakal", Fiyat = 5 }
};

içine bu değerleri atadım ve forech kullanarak elemanları ekrana yazdırdım. List yapısınıa go definiton dedim ve aynısı arrayde olan IEnumerable kullanıldığını gördüm. Ama list kullanarak foreach herhangi bir interface implement etmeden kullanabildim bu nasıl oldu acaba ?

Daha sonra dedi ki...

Anlatım harika olmuş.

private Urun[] _urunSepeti;
public Urun[] UrunSepeti

{

get { return _urunSepeti; }

set { _urunSepeti = value; }

}
burada _urunSepeti değişkeni UrunSepeti property sine set edildiği için;
Dukkan Constructor unda da propertiye değil değişkene eşitlenmeli

public Dukkan()

{

_urunSepeti = UrunleriGetir();

}
olmalı.

Yorum Gönder