-- C#

İleri C# : Jenerik Sınıflar ve İleri Seviye Jenerik İşlemleri – 2

Merhabalar,

.NET Framework mimarisi destekli bir programlama dilleriyle ilgilenen dostların yakinen bildiği terimlerden ikisi aşağıdaki gibidir;

  • Koleksiyonlar (Giriş makalesinde)
  • Jenerikler (Şuan okuduğunuz makalede)

Önceki makalede uzun uzun anlatmaya çalıştığım temel bilgilerden sonra asıl meseleye gelelim. Evet, Jenerikler…

Jenerik Nedir?

Jenerik(Generic) yapılarının temel prensibi, yazılımın programsal tasarım anında tiplerinin belirlenmemiş olmasıdır. Yani bir nesnenin hangi tiple kullanılacağı, ya da içeriğindeki yapıcı metodun(constructer)(sınıf ise) hangi tipte nesne ile kurulacağının o anda belli olmaması ve sınıfı kullanacak geliştiriciye esneklik sunmak gibi durumlarda tercih edilen mimari geliştirme odaklı bir güçlendirilmiş programlama yeteneğidir.

.NET Framework içerisindeki jenerik sınıfları inceleyerek ufak bir bakış yapalım.

Öncelikle using’e namespace olarak jenerikleri ekleyelim;

using System.Collections.Generic;

Sonra testlere başlayabiliriz.

List<string> pLanguages = new List<string>();
pLanguages.Add("C#");
pLanguages.Add("Java");

Dictionary<int, string> pLanguages = new Dictionary<int, string>();
pLanguages.Add(3, "C#");
pLanguages.Add(5, "Java");

Jenerik Sınıflar

Geliştirdiğiniz sınıfların(classes) kurulum(construction) aşamasında hangi tipte nesne ile kurulacağının belirsiz olduğu ya da belirli seçeneklerdeki nesnelerle kurulmasına izin vermek(Jenerik Kısıtlar) ya da hangi nesne tipleriyle KURULAMAMASI gerektiğini belirlemek için jenerik sınıflar(Generic Classes) oluşturmamız gerekir. Aynı şekilde bir sınıfın içerisindeki metotlar da jenerik yapılabilir ki onu Jenerik Metotlar başlığında inceliyor olacağız.

Liste adında .NET Framework içerisindeki List adıyla benzer bir sınıf geliştirelim.

/// <summary>
/// İçi boş bir Liste sınıfı. En temel sınıfımız olarak düşünülebilir.
/// </summary>
class Liste
{

}

/// <summary>
/// Upss! Liste sınıfını overload edebiliyor muşuz!
/// Evet, Liste sınıfını aşırı yükleyerek class overload yaptık.
/// Artık Liste sınıfı tek başına da kurulabilir, dışarıdan T tipini alarak da kurulabilir. T mi?
/// T tipi geliştirme anında belirli olmayan ve sınıfın kurulumu sırasında karar verilecek herhangi bir tipin yerine bir yer tutucu olarak yerleştirildi.
/// Not : var tipini hatırlayın!
/// </summary>
/// <typeparam name="T">Geliştirme anında T olarak ne verilirse class içerisinde T artık o tip olarak kullanılabilmektedir.</typeparam>
class Liste<T>
{
        public Liste()
        {
                // T tipinin typeof() operatörüyle tipinin elde edilip ekrana yazılmasını istedik.
                Console.WriteLine(typeof(T));
        }
}

/// <summary>
/// Liste sınıfımızı ikinci bir overload işlemine soktuk ve artık iki farklı tip parametre ile kurulabilir.
/// Alternatif bir overload!
/// </summary>
/// <typeparam name="T">İlk neesne parametresi T ile temsil edildi.</typeparam>
/// <typeparam name="K">İkinci nesne parametresi K ile temsil edildi.</typeparam>
class Liste<T,K>
{
        // Bu sınıfın(bu overload) kurulumu için T ve K vermek zorunludur.
        // Kurulumda gelen T ve K garanti olduğuna göre sınıf içerisinde de T ile K'yı değişken veri tipi olarak kullanabiliriz.
        T inT;
        K inK;

        public Liste()
        {
                Console.WriteLine("T -> : " + typeof(T));
                Console.WriteLine("K -> : " + typeof(K));
        }

        /// <summary>
        /// Bu sınıfı kurduysanız Add metodu sınıf kurulumundaki T ve K tiplerine göre tip kabul edecektir.
        /// </summary>
        /// <param name="t"></param>
        /// <param name="k"></param>
        public void Add(T t, K k)
        {
                inT = t;
                inK = k;
        }

        public void Get()
        {
                Console.WriteLine("T value -> " + inT.ToString());
                Console.WriteLine("K value -> " + inK.ToString());
        }
}

/// <summary>
/// Ve olayı abarttık! Üç parametre alan Liste sınıf overload'ı...
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="K"></typeparam>
/// <typeparam name="V"></typeparam>
class Liste<T, K, V>
{
        T inT;
        K inK;
        V inV;

        public Liste()
        {
                Console.WriteLine("T -> : " + typeof(T));
                Console.WriteLine("K -> : " + typeof(K));
                Console.WriteLine("V -> : " + typeof(V));
        }

        public void Add(T t, K k, V v)
        {
                inT = t;
                inK = k;
                inV = v;
        }

        public void Get()
        {
                Console.WriteLine("T value -> " + inT.ToString());
                Console.WriteLine("K value -> " + inK.ToString());
                Console.WriteLine("V value -> " + inV.ToString());
        }
}

İlk jenerik sınıfımızı tamamladık. Şimdi onu kullanalım…

// 1. kurulum
Liste<double> dx = new Liste<double>();

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

// 2. kurulum
Liste<int, string> veriler = new Liste<int, string>();
veriler.Add(2, "Siemens");
veriler.Get();

Uygulama çıktısı;

3

 

 

 

 

 

Görüldüğü gibi sınıfın birden fazla aşırı yüklenmiş(overload) halini kullanabildik.

Jenerik Kısıtlar

Jenerik sınıflar geliştirirken programlama tarafında tecrübeli olan arkadaşların aklında bazı soru işaretleri olabilir. Mesela bu sınıfı kurarken her tipi gönderebilir miyim? Peki ben referans tipine göre tasarladım jenerik sınıfı ama kullanan kişi değer tipi(value type) gönderirse ne olur? Hiç iyi şeyler olmaz… Bu gibi sorunları ortadan kaldırmak için jenerik sınıfın programsal mimari tasarımını yaparken bir çok farklı kısıt belirleyebiliyoruz. Şimdi bu kısıtları inceleyeceğiz.

Jenerik listenizin sadece default constructor’ı olan herhangi bir sınıf ile kurulabileceğini belirtmek için;

// T yerine default constructor'ı olan herhangi bir tip gelebilir.
class Demo<T> where T : new()
{

}

Jenerik listenizin sadece referans tipinden(burada class referans tipini temsil eder) herhangi bir tip ile kurulabileceğini belirtmek için;

// T yerine gelen nesne referans türü olmak zorunda olsun.
class Demo<T> where T : class
{
        void DemoMetot(T param)
        {

        }
}

Jenerik listenizin sadece değer tipinden(burada struct değer tipini temsil eder) herhangi bir tip ile kurulabileceğini belirtmek için;

// T yerine gelen nesne değer türü olmak zorunda olsun.
class Demo<T> where T : struct
{
        void DemoMetot(T param)
        {

        }
}

Ya da kendi geliştirdiğimiz bir sınıf mimarisini kullanarak çeşitli kısıtlar belirleyebiliriz.

// Kullanici sınıfından miras(kalıtım) alan referans türlerini(class) alabilir.
class Kullanici
{
        public int ID { get; set; }
        public string Ad { get; set; }
}

class Yonetici : Kullanici
{
        public string Departman { get; set; }
}

/// <summary>
/// Bu overload T tipi olarak sadece Kullanici tipi alabilir.
/// </summary>
/// <typeparam name="T"></typeparam>
class Demo<T> where T : Kullanici
{
        void DemoMetot(T param)
        {

        }
}

/// <summary>
/// Bu overload T tipi olarak sadece Kullanici, V tipi olarak da Yonetici alabilir.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="V"></typeparam>
class Demo<T, V> where T : Kullanici
                       where V : Yonetici
{

}

İyi bir jenerik mimari oluşturmak için jenerik kısıtları kullanabilirsiniz.

Jenerik Metotlar

Geliştirdiğiniz bir sınıfın değil de, içerisindeki metotların jenerik olmasını istiyorsanız bu da mümkün!

class Islem
{
        // Ekle metodu T tipi alıyor ve bu T sadece X sınıf nesnesine ait olabilir.
        public void Ekle<T>(T x) where T: X
        {

        }

        public List<TR> Listele<T1, TR>(T1 xyz)
        {
                List<TR> liste = new List<TR>();
                return liste;
        }
}

class X
{

}

Kullanımı;

X xyz = new X();
Islem islem = new Islem();

// Ekle metodu X nesnesiyle kurulabilir ve parametre de bir X nesne örneği olabilir.
islem.Ekle<X>(xyz);

// Listele'nin ilk parametresi metodun aldığı değeri, ikinci parametre ise döneceği değerin tipini belirtir.
// Bunu örnekle incelemek için .NET Framework içerisindeki delegate'lerin üzerine geliştirilen Action ve Func nesnelerini inceleyebilirsiniz.
var returnVal = islem.Listele<int, string>(7);

Jenerik Interface’ler

Şu ana kadar sınıflar üzerinde jenerikleri inceledik. Ancak jenerikler interface nesneleriyle birlikte de kullanılabilmektedir. Şöyle;

interface IDokuman<T>
{
        void Isle(T dokuman);
}

class PDF
{

}

class DOCX
{

}

class Donustur : IDokuman<PDF>, IDokuman<DOCX>
{
        public void Isle(PDF dokuman)
        {

        }

        public void Isle(DOCX dokuman)
        {

        }
}

Bir makalemizin daha sonuna geldik. Kendi jenerik sınıflarımızı, metotlarımızı ve interface nesnelerimizi oluşturduk. Jenerik sınıflar içerisinde ya da jenerik olmayan sınıflarda jenerik metotlar oluşturmayı inceledik.

İyi çalışmalar
Cihan Özhan

Yorumla

Yorum