31 Ekim 2011 Pazartesi

C# Member Hiding

Nesne yönelimli programlamanın temel felsefelerinden birisi kalıtımdır( inheritance). Kalıtım yolu ile taban sınıfın public veya protected olan üyelerine sahip olabilmek söz konusu idi. Ancak bazı üyeleri aynı isim altında tekrar tanımladığımız durumlar olabilmektedir.  Bu durumda compiler’ın nasıl davranacağını incelemeye çalışacağız.

Urun adında taban sınıfımız olsun. Daha sonra da Kitap adında bir sınıf tasarlayalım ve Urun sınıfından kalıtalım.

class Urun

    {

        public int Id { get; set; }

        public string Ad { get; set; }

 

        public void Test()

        {

            Console.WriteLine("Ben Urun sınıfının üyesiyim.");

        }

    }

 

class Kitap : Urun

    {

        public string Yazar { get; set; }    

    }

Kitap tipinden bir nesne örneklediğimiz zaman, Test adlı public metoda erişim yapılabilir. Urun sınıfından kalıtım yapılarak, Test adındaki üye, Kitap tipinden nesneler için erişilebilir hale gelmiştir.

class Program

    {

        static void Main(string[] args)

        {

            Kitap kitap = new Kitap();

            kitap.Test();

        }

    }

Programı çalıştıralım.

image

Test adlı üyeyi, Kitap sınıfı içerisinde de tanımlayalım.

class Kitap : Urun

    {

        public string Yazar { get; set; }

 

        public void Test()

        {

            Console.WriteLine("Ben Kitap sınıfının üyesiyim.");

        }

    }

Yine Kitap tipinden bir nesne örnekleyerek Test metodunu çağıralım.

class Program

    {

        static void Main(string[] args)

        {

            Kitap kitap = new Kitap();

            kitap.Test();

        }

    }

Programı çalıştıralım.

image

Gördüğünüz gibi, taban sınıftaki metot yok sayılmış ve Kitap sınıfındaki metot çalışmıştır. Ancak programı çalıştırırken derleyici aşağıdaki gibi bir uyarı verir.

'MemberHiding.Kitap.Test()' hides inherited member 'MemberHiding.Urun.Test()'. Use the new keyword if hiding was intended.

Kitap sınıfındaki Test adlı üyenin, Urun sınıfında saklı olduğu ve new anahtar sözcüğünün kullanılması gerektiği belirtilmiştir. “new” anahtar sözcüğünü kullanarak compiler’a, Urun sınıfındaki aynı isimli üyeyi görmezden gelmesini söylüyoruz.  Kodumuzu aşağıdaki hale getirip çalıştıralım.

class Kitap : Urun

    {

        public string Yazar { get; set; }

 

        //new public void Test() şeklinde de yazabiliriz.

        public new void Test()

        {

            Console.WriteLine("Ben Kitap sınıfının üyesiyim.");

        }

    }

Kitap tipinden bir nesne örneklediğimizde, aynı sonucu alacağız.

class Program

    {

        static void Main(string[] args)

        {

            Kitap kitap = new Kitap();

            kitap.Test();

        }

    }

image

Kitap sınıfından kalıtılan başka bir sınıf içerisinde de Test adlı üyeyi tanımlayalım.

class A : Kitap

    {

        public new void Test()

        {

            Console.WriteLine("A sınıfındaki metot çalıştı");

        }

    }

A tipinde bir nesne örnekleyerek Test metodunu çağıralım.

class Program

    {

        static void Main(string[] args)

        {

            A a = new A();

            a.Test();

        }

    }

Programı çalıştırdığımızda A sınıfındaki metodun çalıştığını göreceğiz.

image

 

Şimdi de olaya başka bir açıdan yaklaşalım. Kitap, bir Urun olduğundan dolayı, Kitap tipinden bir nesne, Urun tipinden bir değişken ile işaret edilebilir. Bu durumda hangi üyenin çalışacağını test etmeye çalışalım.

class Program

    {

        static void Main(string[] args)

        {

            Urun urun = new Kitap();

            urun.Test();

        }

    }

Programı çalıştıralım.

image

NOT : Member hiding yaptığımız zaman, işaretçinin tipine bakılır. İşaretçi hangi tipte ise, o tip içerisinde tanımlanmış olan üye çalışır. Ancak virtual üyelerde durum tam tersidir. override edilen bir üye için öncelikle eşitliğin sağ tarafı, yani nesnenin tipi önemlidir. Eğer Test adlı metot virtual olsaydı ve Kitap sınıfında override edilseydi, Yukarıdaki kodu çalıştırdığımızda, Kitap sınıfındaki metot çalışacaktı. İşaret edilen nesne Kitap tipinden olduğu için, override edilen metot çalışacaktı.



Geçtiğimiz günlerde, foreach iterasyonu ve yield operatörü ile ilgili detaylı bilgileri sizlerle paylaşmıştım. Kısaca hatırlamaya çalışalım. Kendi yazdığımız bir tip içerisinde foreach ile iterasyon yapılması için IEnumerable interface’ine ihtiyaç duymuştuk. Bu interface ile sınıfa GetEnumerator metodunu kazandırmıştık. GetEnumerator metodu da geriye IEnumerator interface’ini implement eden bir sınıf döndürmekteydi. Bu sınıfın da iterasyonu sağladığını görmüştük. Ancak, current property’si geriye object döndürdüğü için, iterasyon sonucunda elde ettiğimiz her nesneyi istediğimiz tipe cast etmemiz gerekmişti.

Makalemizin konusu da tam olarak bu noktada devreye giriyor. Çünkü burada tip güvenliği söz konusudur. Yazmış olduğumuz sınıf zaten Urun tipinden nesneler içerdiğinden dolayı, iterasyon sonucunda sadece Urun tipinden nesnelerin elde edilebilmesini sağlamaya çalışacağız. Bunu da IEnumerable interface’nin generic versiyonunu kullanarak yapacağız. ArrayList örneğinde de benzer bir durum söz konusuydu, içeride object tipinden elemanlar bulunduğu için, yaptığımız her atamada uygun bir tipe cast etmemiz gerekmekteydi. Dolayısıyla aynı tipten nesneleri bir arada tutmak için generic olan List<T> koleksiyonunu kullanmayı tercih etmiştik.

Daha önceki örneklerdeki gibi Urun sınıfı üzerinden gideceğiz. UrunYonetim adında bir sınıf daha yazacağız ve bu sınıf arka tarafta bir Urun dizisi tutacak. Daha sonra da bu tip üzerinden iterasyon yapmaya çalışacağız.

foreach iterasyonu başlıklı makalemizde generic olmayan IEnumerable interface’inden detaylı olarak bahsettiğimden dolayı direkt olarak generic kullanımının örneğine geçmek istiyorum. Aynı örneği generic interface kullanarak yapacağız.

public class Urun

    {

        public int Id { get; set; }

        public string Ad { get; set; }

        public double Fiyat { get; set; }

 

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

        {

            Id = id;

            Ad = ad;

            Fiyat = fiyat;

        }

    }

Arka tarafta Urun sınıfını kapsülleyen ve iterasyon yapılabilen UrunYonetim adlı sınıfı tasarlayalım.

public class UrunYonetim : IEnumerable<Urun>

    {

        Urun[] urunler;

 

        public Urun[] Urunler

        {

            get { return urunler; }

            set { urunler = value; }

        }

 

        private Urun[] UrunleriGetir()

        {

            return new Urun[]

            {

                new Urun(1,"Monitör",200),

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

                new Urun(3,"Mouse",40)

            };

        }

 

        public UrunYonetim()

        {

            Urunler = UrunleriGetir();

        }

 

        public IEnumerator<Urun> GetEnumerator()

        {

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

                yield return Urunler[i];

        }

 

        IEnumerator IEnumerable.GetEnumerator()

        {

            return this.GetEnumerator();

        }

    }

Burada dikkat edilmesi gereken nokta, generic olan interface’I implement ettiğimizde sınıfa gelen iki üyedir. Birisi IEnumerable<T> ile gelen üyedir. İkincisi ise IEnumerable sınıfı ile gelen üyedir. Generic versiyonu kullanmamız halinde, generic olmayan versiyonun da uygulanması zorunlu kılınmış. Çünkü 2005 öncesinde generic mimari yoktu ve foreach iterasyonu için IEnumerable interface’i zorunluydu. Ancak generic mimari ile birlikte IEnumerable<T> zorunlu hala geldi. Örneğin string tipi IEnumerable<char> tipini implement etmiştir. Dolayısı ile generic mimariden sonra, eski kodlar geçerliliğini yitirecekti. Bu sebepten dolayı, generic interface’i implement eden tüm üyerele IEnumerable interface’ini implement etme zorunluluğu getirilmiştir.  Dolayısı ile iki üyeye de gövde kazandırmamız gerekiyor. Metotların isimleri de aynı olduğundan dolayı, TipAdi.UyeAdi notasyonu kullanılmış. İçerisinde de, generic olan IEnumerable metodu ile gelen üyeyi çağırdık.

Şimdi de Main metodu içerisinde iterasyonu sağlayıp geriye dönen elemanları inceleyelim.

static void Main(string[] args)

        {

            UrunYonetim dukkan = new UrunYonetim();

 

            IEnumerator<Urun> e = dukkan.GetEnumerator();

 

            while (e.MoveNext())

            {

                Console.WriteLine(e.Current.Ad);

            }

        }

dukkan işaretçisi üzerinden GetEnumerator metodu çağırıldığında geriye Iterasyonu yapan IEnumerator sınıfı döner. Bunun da generic versiyonu kullanılmıştır. yield operatörü sayesinde arka tarafta IEnumerator<Urun> interface’ini implement eden bir sınıf oluşmaktadır. Dolayısı ile Current property’si geriye object değil Urun tipini dönmektedir. Bu sayede de tip güvenliğini sağlamış oluruz.



Önceki makalelerimden birinde generic kavramının ne olduğundan olduğundan kısaca bahsetmiştim. Bugün de bir metodu nasıl generic hale getireceğimizi inceleyeceğiz. Asıl düşünülmesi gereken nokta nasıl değil, neden yapmamız gerektiğidir.

Bildiğiniz gibi ArrayList sınıfı arka tarafta bir object dizisi örnekler ve başlangıçta boyutu 4’dür. ArrayList’e elemanlar ekledikçte arka taraftaki bu object dizisinin boyutu ihtiyaç oldukça iki katına çıkartırılır.

Arka taraftaki dizi boyutlandırma işlemini manuel olarak yapmaya çalışalım. DiziBoyutlandır diye bir metodumuz olsun. Parametre olarak bir dizinin referansını alsın ve geriye void döndürsün.

NOT: Parametreyi referans olarak geçtiğimiz için, yaptığımız değişiklik metodu çağırırken parametre olarak verilen işaretçi üzerinde gerçekleşecektir.

class Program

    {

        static void DiziBoyutlandir(ref int[] dizi)

        {

            int[] boyutlandirilmisDizi = new int[dizi.Length * 2]; //parametre olarak gelen dizinin 2 katı boyutuna sahip yeni bir dizi ürettik.

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

            {

                boyutlandirilmisDizi[i] = dizi[i]; //parametre olarak gelen dizi içerisindeki elemanları yeni oluşturduğumuz diziye ekliyoruz.

            }

 

            dizi = boyutlandirilmisDizi; //dizi adlı işaretçi Main metodu içerisindeki 'sayilar' değişkenini temsil etmektedir. Dolayısı ile 'sayilar'değişkeni artık yeni oluşturulan diziyi işaret edecek.

        }

 

        static void Main(string[] args)

        {

            int[] sayilar = { 1, 2, 3, 4 };

            foreach (int sayi in sayilar)

            {

                Console.WriteLine(sayi);

            }

 

            Console.WriteLine("******************************************");

 

            DiziBoyutlandir(ref sayilar);

            foreach (int sayi in sayilar)

            {

                Console.WriteLine(sayi);

            }

        }

    }

Main metodu içerisinde oluşturduğumuz sayilar adlı 4 elemanlı dizinin elemanlarını ekrana yazdırıyoruz. Ardından diziyi yeniden boyutlandırarak, elemanları tekrar ekrana yazdırıyoruz. Ekran çıktısı aşağıdaki gibi olacaktır.

image

Dizimizin yeniden boyutlandırılmış halini ekrana yazdırdığımızda 8 elemanlı olduğunu ve sondaki 4 elemana integer tipinin default değeri olan 0’ın atandığını görürüz.

Şimdi de double tipinden bir diziyi boyutlandıran metoda ihtiyacımız olduğunu varsayalım. DiziBoyutlandir adlı metodu overload edelim.

static void DiziBoyutlandir(ref double[] dizi)

        {

            double[] boyutlandirilmisDizi = new double[dizi.Length * 2];

           

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

            {

                boyutlandirilmisDizi[i] = dizi[i];

            }

 

            dizi = boyutlandirilmisDizi;

        }

Byte dizisini boyutlandıran bir metoda ihtiyacımız olduğunu varsayarsak aynı metodu 2.kez aşırı yükleyebiliriz.

static void DiziBoyutlandir(ref byte[] dizi)

        {

            byte[] boyutlandirilmisDizi = new byte[dizi.Length * 2];

           

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

            {

                boyutlandirilmisDizi[i] = dizi[i];

            }

 

            dizi = boyutlandirilmisDizi;

        }

Farkettiğiniz üzere, aynı işlemi birden fazla kez tekrarlamış olduk. Metotların yaptığı işler birebir aynı olmasına rağmen tek fark, farklı bir tip ile işlem yapılmasıdır. İşte bu noktada generic kavramı devreye girmektedir. Biz int, double veya byte değil de T tipinden bir dizi için generic bir metot yazarsak, tüm tipler için istediğimiz işlemi gerçekleştiren bir metoda sahip oluruz.

static void DiziBoyutlandir<T>(ref T[] dizi)

        {

            T[] boyutlandirilmisDizi = new T[dizi.Length * 2];

 

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

            {

                boyutlandirilmisDizi[i] = dizi[i];

            }

 

            dizi = boyutlandirilmisDizi;

        }

Artık herhangi bir tipten verilen bir diziyi boyutlandırabilecek bir metoda sahibiz. Bir metot yazıp defalarca aşırı yüklemek yerine, tek bir metot ile amacımızı gerçekleştirmiş olduk.

Daha önceki makalelerimden birinde Generic tipler ile ilgili bazı kısıtlamalardan bahsetmiştim. Aynı kısıtlamaları generic metotlara uygulayabilmemiz de mümkündür. Mesela DiziBoyutlandir metodundaki T tipinin yalnızca sayısal tipler olabileceğini vurgulayabiliriz. Bunun için yapmamız gereken “where” anahtar sözcüğü ile gerekli kısıtı belirtmektir.

static void DiziBoyutlandir<T>(ref T[] dizi) where T : struct

        {

            T[] boyutlandirilmisDizi = new T[dizi.Length * 2];

 

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

            {

                boyutlandirilmisDizi[i] = dizi[i];

            }

 

            dizi = boyutlandirilmisDizi;

        }

Bu durumda T yerine referans türlü bir tip gönderirsek derleme zamanında hata alacağız. Örneğin, metodu çağırırken parametre olarak string tipinden bir dizi  gönderirsek aşağıdaki gibi bir hata mesajı ile karşılaşacağız.

The type 'string' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'GenericMetot.Program.DiziBoyutlandir<T>(ref T[])'  

Son olarak, yazdığımız generic metodu Main metodu içerisinde test edelim.

static void Main(string[] args)

        {

            Console.WriteLine("Integer dizisi için...");

 

            int[] sayilar = { 1, 2, 3, 4 };

            foreach (int sayi in sayilar)

            {

                Console.WriteLine(sayi);

            }

 

            Console.WriteLine("******************************************");

 

            DiziBoyutlandir(ref sayilar); // T tipi çalışma anında int olarak davranacak

            foreach (int sayi in sayilar)

            {

                Console.WriteLine(sayi);

            }

 

            Console.WriteLine();

            Console.WriteLine("Double dizisi için");

 

            double[] ondalikSayilar = { 1.4, 5.76, 3.9, 10.5 };

 

            foreach (double sayi in ondalikSayilar)

            {

                Console.WriteLine(sayi);

            }

 

            Console.WriteLine("******************************************");

 

            DiziBoyutlandir(ref ondalikSayilar);  // T tipi çalışma anında double olarak davranacak

            foreach (double sayi in ondalikSayilar)

            {

                Console.WriteLine(sayi);

            }

        }

Alacağımız ekran görüntüsü ise aşağıdaki gibi olacaktır.

image