5 Ekim 2011 Çarşamba

C# Koleksiyonlar

Programlamadaki temel ihtiyaçlarımızın biri de verilerimizin toplu olarak tutulduğu yapılardır. Bu ihtiyaçlarımızı genellikle diziler ya da koleksiyonlar ile gideririz.  Bu yazımızda .Net içerisinde yer alan bazı koleksiyonları inceliyor olacağız.

İlk olarak ArrayList ile başlayalım. Arraylist, içerisinde object tipinden nesneler tutabilen bir tiptir. Çalışma mantığını ise encapsulation felsefesi ile özdeşleştirebiliriz. Çünkü arka tarafta bir object dizisi oluşturur ve işlemleri bu dizi üzerinden yapar. Yani “ArrayList, bir object dizisini kapsüllemiştir” dersek doğru bir cümle kurmuş oluruz. .Net’deki tüm tiplerin  object’den kalıtıldığını düşünürsek, ArrayList’in içerisinde object tutması ile bizim için çok esnek bir yapı sağladığını düşünebiliriz. Ancak bu durumun 2 önemli dezavantajı da mevcuttur. İçerisine eklemek istediğimiz eleman değer türlü bir tipse, boxing olayı meydana gelecektir çünkü içerisine eklenen eleman object olarak tutulur. Bu da performans açısından pek tercih edilen bir durum değildir. Diğer bir dezavantajı ise, okuma veya yapacağımız zaman cast işlemi yapmamız gerektiğidir. Yine aynı mantık ile, içeride object tipinden nesneler tutulduğundan ötürü doğru tipe cast etme işlemi programcıya düşer. Kullanımına basit bir örnek vermeye çalışalım.

class Program

    {

        static void Main(string[] args)

        {

            ArrayList arrayList = new ArrayList();

            arrayList.Add(new object());

            arrayList.Add(10);

            arrayList.Add(3.14);

            arrayList.Add("metinsel ifade");

            arrayList.Add(new int[] { 1, 2, 3, 4, 5, 6, 7, 8 });

            arrayList.Add(new SqlConnection());

            arrayList.Add('c');

 

            Console.WriteLine(arrayList.Count); // ekran çıktısı => 7

 

            ////////////////////////////////////////////////////////////////

 

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

            ArrayList liste = new ArrayList();

 

            foreach (int sayi in sayilar)

            {

                liste.Add(sayi); // her ekleme sırasında boxing olayı meydana gelecektir.

            }

 

            //liste.AddRange(sayilar);

            //diziler Array tipinden kalıtılır. Array tipi de ICollection interface'ini implement ettiğinden ötürü AddRange metoduna parametre olarak verebiliriz. Yukarıdaki döngü yerine direkt bu işlemi yapabiliriz.

 

            int[] yeniDizi = new int[sayilar.Length];

 

            for (int i = 0; i < yeniDizi.Length; i++)

            {

                yeniDizi[i] = (int)liste[i]; //listenin içerisindeki her bir eleman object olduğundan cast işlemi yapmak zorundayız. Unboxing olayı meydana gelir.

            }

        }

    }

ArrayList’in kullanımı en basit anlamda yukarıdaki gibidir. Generic mimari ile birlikte gelen List<> koleksiyonu, yukarıda saydığımız dezavantajları ortadan kaldırmaktadır.  Örneğin giriş kısmında eklediğimiz SqlConnection, char, string vb türler sadece örnek vermek amacı ile yazılmıştır. Zaten çoğu durumda bir koleksiyon içersinde aynı tipten nesneler tutarız. Bu yüzden ArrayList kullanmak pek hoş olmayacaktır. Şimdi List kullanımına göz atalım.

class Program

    {

        static void Main(string[] args)

        {

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

            List<int> liste = new List<int>(); //içeride sadece integer tipinden nesneler olabilir.

 

            foreach (int sayi in sayilar)

            {

                liste.Add(sayi); // Add metodu bizden integer beklediği için boxing olayı meydana gelmez.

            }

 

            int[] yeniDizi = new int[sayilar.Length];

 

            for (int i = 0; i < yeniDizi.Length; i++)

            {

                yeniDizi[i] = liste[i]; //listenin içerisindeki her bir eleman object olduğundan cast işlemi yapmak zorundayız.

            }

 

            foreach (int sayi in yeniDizi)

            {

                Console.WriteLine(sayi); //iterasyon yaparken int harici bir tip kullanırsak derleme zamanı hatası alırız. Ancak ArrayList kullanırsak çalışma zamanı hatası alırız. List kullanımı, bizi doğru tip ile iterasyon yapmaya zorlamış olur, dolayısı ile çalışma zamanında hata almayız.

            }

        }

    }

Yaptığımız örnek üzerinden konuşursak, List tipinin ArrayList’e göre daha kullanışlı olduğunu söyleyebiliriz. Ancak tekrar etmekte fayda var, her zaman ihtiyaçlarımız doğrultusunda en uygun tipi kullanmalıyız. List’in daha kullanışlı olduğunu bu örnek için belirttiğimiz hatırlatmakta fayda var.

Bir başka koleksiyona geçelim ve Stack tipini inceleyelim.. Yığın anlamına gelir ve LIFO (son giren ilk çıkar) prensibi ile çalışır. Hem normal hem de generic kullanımları söz konusudur. İhtiyacımıza göre bu iki kullanımdan birini tercih edebiliriz. Ama yukarıda da bahsettiğim gibi, çoğu zaman aynı tipten nesneler tutmak istediğimizden ötürü genelde Generic versiyonu tercih edebiliriz. Bu kullanım ile boxing-unboxing olaylarının önüne geçeriz.

class Program

    {

        static void Main(string[] args)

        {

            #region Non-Generic Kullanım

 

            Stack yigin = new Stack();

 

            for (int i = 1; i <= 5; i++)

            {

                yigin.Push(i); //boxing olayı meydana gelir. Çünkü push metodu bizden object ister.

 

                //Push metodu ile yığın içerisine eleman ekleme işlemini gerçekleştiririz.

            }

 

            //yigin içerisinde iterasyon yapmak istersek.

            foreach (int item in yigin)

            {

                Console.WriteLine(item); //içeride object tipinden nesneler olduğundan ötürü iterasyon sırasında yanlış bir tip kullanma riskimiz vardır. Bu da çalışma zamanında hata almamızı sağlar. Bu örnek için, içeride integer tipinden nesneler olduğunu bildiğimizden ötürü hata almayacağız. Ancak başkta tipten nesneler olsaydı çalışma zamanında uygun tipe dönüştürülemeyen durumlarda exception ile karşılaşacaktık.

            }

 

            int[] sayilar = new int[yigin.Count];

 

            for (int i = 0; i < sayilar.Length; i++)

            {

                sayilar[i] = (int)yigin.Pop(); //son giren elemanı okur ve yığından çıkartır. Geriye object döndüğünden cast işlemi yapmalıyız. Dolayısı ile unboxing olayı meydana gelir.

 

                //sayilar[i]=(int)yigin.Peek(); dersek, yine son elemanı okur ama yığından çıkarmaz. Dolayısı ile dizimize hep yığının son elemanı eklenir. Yine geriye object döndürdüğünden dolayı cast işlemi yapmamız gerekir.

            }

 

            foreach (int sayi in sayilar)

            {

                Console.WriteLine(sayi);  //Ekran çıktısı 5 4 3 2 1 şeklinde olacaktır. LIFO prensibi söz konusudur.

            }

 

            #endregion

 

 

            #region Generic Kullanım

 

            Stack<int> genericYigin = new Stack<int>();

 

            for (int i = 1; i <= 5; i++)

            {

                genericYigin.Push(i); //boxing olayı meydana gelmez. Çünkü push metodu bizden integer ister.

 

                //Push metodu ile yığın içerisine eleman ekleme işlemini gerçekleştiririz.

            }

 

            //genericYigin içerisinde iterasyon yapmak istersek.

            foreach (int item in genericYigin)

            {

                Console.WriteLine(item); //içeride integer tipinden nesneler olduğundan ötürü iterasyon sırasında başka bir tip kullanamayız. Aksi takdirde derleme zamanı hatası alırız.

            }

           

            int[] sayilar2 = new int[genericYigin.Count];

 

            for (int i = 0; i < sayilar2.Length; i++)

            {

                sayilar2[i] = genericYigin.Pop(); //son giren elemanı okur ve yığından çıkartır. Geriye integer döndüğünden cast işlemi yapmamıza gerek yoktur. Dolayısı ile unboxing olayı meydana gelmez.

 

                //sayilar2[i] = genericYigin.Peek(); dersek, yine son elemanı okur ama yığından çıkarmaz. Dolayısı ile dizimize hep yığının son elemanı eklenir ve dizinin tüm elemanları 5 olur

 

            }

 

            foreach (int sayi in sayilar2)

            {

                Console.WriteLine(sayi);  //Ekran çıktısı 5 4 3 2 1 şeklinde olacaktır.

            }

                       

            #endregion

        }

    }

Şimdi de FIFO(ilk giren ilk çıkar) prensibi ile çalışan Queue koleksiyonunu inceleyelim. Stack yapısı ile çok benzer olan bu tipin tek farkı çalışma prensibinin FIFO olmasıdır. Yukarıdaki örneğin aynısnı Queue için yapalım.

class Program

    {

        static void Main(string[] args)

        {

            #region Non-Generic Kullanım

 

            Queue kuyruk = new Queue();

 

            for (int i = 1; i <= 5; i++)

            {

                kuyruk.Enqueue(i); //boxing olayı meydana gelir. Çünkü Enqueue metodu bizden object ister.

 

                //Enqueue metodu ile kuyruk içerisine eleman ekleme işlemini gerçekleştiririz.

            }

 

            //kuyruk içerisinde iterasyon yapmak istersek.

            foreach (int item in kuyruk)

            {

                Console.WriteLine(item); //içeride object tipinden nesneler olduğundan ötürü iterasyon sırasında yanlış bir tip kullanma riskimiz vardır. Bu da çalışma zamanında hata almamızı sağlar. Bu örnek için, içeride integer tipinden nesneler olduğunu bildiğimizden ötürü hata almayacağız. Ancak başkta tipten nesneler olsaydı çalışma zamanında uygun tipe dönüştürülemeyen durumlarda exception ile karşılaşacaktık.

            }

 

            int[] sayilar = new int[kuyruk.Count];

 

            for (int i = 0; i < sayilar.Length; i++)

            {

                sayilar[i] = (int)kuyruk.Dequeue(); //ilk giren elemanı okur ve kuyruktan çıkartır. Geriye object döndüğünden cast işlemi yapmalıyız. Dolayısı ile unboxing olayı meydana gelir.

 

                //sayilar[i]=(int)kuyruk.Peek(); dersek, yine son elemanı okur ama yığından çıkarmaz. Dolayısı ile dizimize hep kuyruğun ilk elemanı eklenir. Yine geriye object döndürdüğünden dolayı cast işlemi yapmamız gerekir.

            }

 

            foreach (int sayi in sayilar)

            {

                Console.WriteLine(sayi);  //Ekran çıktısı 1 2 3 4 5 şeklinde olacaktır. LIFO prensibi söz konusudur.

            }

 

            #endregion

 

 

            #region Generic Kullanım

 

            Queue<int> genericKuyruk = new Queue<int>();

 

            for (int i = 1; i <= 5; i++)

            {

                genericKuyruk.Enqueue(i); //boxing olayı meydana gelmez. Çünkü Enqueue metodu bizden integer ister.

 

                //Dequeue metodu ile kuyruk içerisine eleman ekleme işlemini gerçekleştiririz.

            }

 

            //genericYigin içerisinde iterasyon yapmak istersek.

            foreach (int item in genericKuyruk)

            {

                Console.WriteLine(item); //içeride integer tipinden nesneler olduğundan ötürü iterasyon sırasında başka bir tip kullanamayız. Aksi takdirde derleme zamanı hatası alırız.

            }

 

            int[] sayilar2 = new int[genericKuyruk.Count];

 

            for (int i = 0; i < sayilar2.Length; i++)

            {

                sayilar2[i] = genericKuyruk.Dequeue(); //ilk giren elemanı okur ve kuyruktan çıkartır. Geriye integer döndüğünden cast işlemi yapmamıza gerek yoktur. Dolayısı ile unboxing olayı meydana gelmez.

 

                //sayilar2[i] = genericKuyruk.Dequeue(); dersek, yine son elemanı okur ama kuyruktan çıkarmaz. Dolayısı ile dizimize hep kuyruğun ilk elemanı eklenir ve dizinin tüm elemanları 5 olur

 

            }

 

            foreach (int sayi in sayilar2)

            {

                Console.WriteLine(sayi);  //Ekran çıktısı 1 2 3 4 5 şeklinde olacaktır.

            }

 

            #endregion

        }

    }

 



4 yorum:

Unknown dedi ki...

Merhaba.
Bir soru sormak istoyurm.Belki cevaplarsiniz.

Diyelim ki bir proge icerisinde Departments diye bir class olusturmak istiyorum.
Name, Id ve Positions olsun her Departmanda.
Burada onceden belirlenmis Position-lar olmasini istiyorum. Meselea bir
List positions = new List ( ) {......}
Yani kullanici yalnis Position ismi girdikde hata vericek.
Sorum soyle :
Bu position-lari Department sinifina property gibi mi vermeliyim.
Yoksa her defe Deparment sinifi olusturuldugunda mi poszisyonlarin da olusmasini mi saglamaliyim ? Ve ya baska ?
Databse kullanmiyorum. Sadece Console ortaminda OOP-yi ogrenmek icin yapiyorum bunu.
Iyi gunler

Onur Salkaya dedi ki...

Bahsettiğiniz "Position" tek bir değere sahip ise (örneğin string) List < string > Positions... şeklinde bir property tanımlanabilir. Eğer Position nesnesi birden fazla özelliğe sahip olacaksa Position adında bir sınıf tasarlanabilir ve Department sınıfına List < Position > Positions... adında bir property eklenebilir.

Bu değerler de eğer DB kullanılmıyorsa nesne örneği ram'e çıktığında oluşturulabilir. Ancak çok sık değişen değerler değilse static constructor kullanılabilir.

Unknown dedi ki...

Cok Tesekkur ederim cevab verdginiz icin.

Adsız dedi ki...

Merhaba, Öncelikle değerli anlatımınız için teşekkür ederim.

yeniDizi[i] = liste[i]; //listenin içerisindeki her bir eleman object olduğundan cast işlemi yapmak zorundayız.
(Buradaki açıklamanızda; her bir eleman int olduğundan cast işlemi yapmayız.) olması gerekmiyor mu?

Yorum Gönder