C# Generics

GenericsGeneric’ler tasarlandığımız interface, class, metod yada parametrelerin (argümanların) belirli bir tip için değil bir şablon yapısına uyan her tip için çalışmasını sağlayan bir yapıdır. Generic’ler C++’taki şablon (template)’lerle benzerlik göstermesine karşın, uygulanış (implementation) bakımından farklılıklar göstermektedir. Generic ve C++ şablon (template)’lar arasındaki farklılıkları bu linkten inceleyebilirsiniz. Generic’ler.Net 2.0 ile birlikte kullanıma sunulmuştur.

Generic’lerin bize sağladığı avantajlar :

  • Yazılım parçacıkları içerisinde tekrar kullanılabilir kod yazmamıza yardımcı olarak kod tekrarını önler.
  • Kaliteli ve daha yönetilebilir kod yazmamıza olanak sağlar.
  • Çalışma zamanında (run time) gereksiz Cast ve Boxing-Unboxing kullanmasını önlediğinden efektif performans sağlar.
  • Derleme zamanında (compile time) (type safe) tip güvenli değişken kullanılmasını zorlayarak çalışma zamanında oluşabilecek tip dönüşüm hatalarını önler.
  • Programcıya kod üzerinde daha güçlü esnek bir kontrol sağlar.


Generic Koleksiyonlar (Generics Collections)

Generic koleksiyonlar System.Collections.Generics namespace’i sayesinde kullanılabilir. Bu namespace içirisinde çeşitli parametreleştirilmiş koleksiyon/konteynır sınıfları barındırmaktadır. Bunları kullanmak için sadece koleksiyonun tipini belirtip parametre olarak geçmek yeterlidir.

Örneğin :

List<int> myList = new List<int>();
myList.Add(3);
myList.Add(4);
// myList.Add(5.0);
int total = 0;
foreach(int val in myList)
{
        total = total + val;
}
Console.WriteLine("Toplam : {0}", total);

Yukarıda dikkat edileceği üzere, <> içerisinde int tipini parametre olarak girip generic List değişken (instance )’i oluşturuyoruz. Bu kod çalıştığında bize “Toplam : 7” çıktısını verecektir. Eğer yorumlanmış (comment) olan doubleList.Add(5.0); satırının yorumunu kaldırırsak derleme hatası (compile error) aldığımızı gözlemleyebiliriz. Derleyici (compiler) 5.0 değerini Add() metoduna parametre olarak kabul etmez ve sadece int tipinde değer kabul edeceğini bildirir. Böylece derleme anında tip güvenliği sağlanmış olur.

Generic Koleksiyonlar

Yukarıdaki resim koleksiyonların kalıtım (inheritance) yapısını göstermektedir. Kalıtım yolu ile List<T> koleksiyonunun kazandığı ek özellikler aşağıdaki gibidir.

  • T tipindeki objeleri koleksiona Add veya Insert etmek
  • Silme (Removal) metodları
    • Remove, RemoveAt, RemoveRange, RemoveAll
  • Sıralama (Sorting) metodları
    • Sort (Comparison<T>)
    • Reverse
  • Çevirim (Conversion) metodları
    • ConvertAll (Converter<T,U>)
    • CopyTo(T[])
  • Arama (Searching) (Predicate<T>) metodları
    • Exists
    • Find, FindAll, FindLast
    • FindIndex, FindLastIndex, IndexOf, LastIndexOf
    • TrueForAll
  • Döngü (Iterating) metodu
    • ForEach (Action<T>)

Görüldüğü üzere generic koleksiyon kullandığımızda bir çok yardımcı metod kullanıma hazır gelmekte ve işimizi kolaylaştırmaktadır. Bu metodlar aşağıdaki resimdede görüleceği üzere dictionary yapısında olan koleksiyonlar tarafındanda uygulanmış (implement) ve kullanılabilir haldedir.

Generic Dictionaries

Generic dictionary kullanımına ilişkin bir örnek yapacak olursak :

Dictionary<string, int> numbers = new Dictionary<string, int>();
numbers["Bora"] = 862842;
numbers["Fuat"] = 951753;
numbers["Erçin"] = 123654;
foreach (KeyValuePair<string, int> entry in numbers)
         Console.WriteLine(entry.Key + ":" + entry.Value);

Dictionary koleksiyonlarda çokça bilinmeyen bir kullanım şekli :

var newDic = new Dictionary<int,string> {{1,"a"}, {2, "b"}};

Dictionary<int, string> myDict = new Dictionary<int, string>()
            {
                { 2, "Bu" },
                { 1, "bir" },
                { 5, "sözlük" },
                { 12, "denemesidir." },
            };

List dışında .Net içerisinde bulunan koleksiyonlar aşağıdaki gibidir.

Generic Class veya InterfaceAçıklamaGeneric Olmayan Karşılığı
Collection<T>
ICollection<T>
Generic koleksiyon için ana (base) sınıf desteği sağlar.CollectionBase
ICollection
Comparer<T>
IComparer<T>
IComparable<T>
Aynı Generic tipteki iki objenin eşitlik (equivalence ) ve sıralama (sorting) işlemleri temsil eder.Comparer
IComparer
IComparable
Dictionary<K, V>
IDictionary<K,V>
Key/Value çiftinden oluşmuş koleksiyonu temsil eder. Key bazında organize edilmiştir.Hashtable
IDictionary
Dictionary<K, V>.KeyCollectionDictionary 'deki key koleksiyonunu temsil eder.
Yok
Dictionary<K,V>.ValueCollectionDictionary 'deki valuekoleksiyonunu temsil eder.
Yok
IEnumerable<T>
IEnumerator<T>
Foreach kullanılarak üzerinde dolaşılabilecek (iterate) koleksiyonu temsil eder.IEnumerable
IEnumerator
KeyedCollection<T, U>Key bazlı koleksiyonu temsil eder.KeyedCollection
LinkedList<T>Bağlı listeleri temsil eder.
Yok
LinkedListNode<T>Bağlı listenin LinkedList düğümünü (node) temsil eder.
Yok
List<T>
IList<T>
Dizi (array) boyutunun dinamik olarak arttığı durumlarda IList interface'i ile genişletme ihtiyacı olan durumları temsil eder.
ArrayList
IList
Queue<T>(First In First Out) İlk giren ilk çıkar türündeki obje koleksiyonlarıını temsil eder.Queue
ReadOnlyCollection<T>Sadece okunabilen (read-only) Generic koleksiyonlar için ana (base) sınıf desteğini temsil eder.ReadOnlyCollectionBase
SortedList<K, V>Key/Value çiftlerinden oluşan IComparer interfacı ile genişletilmiş key bazlı sıralanmış koleksiyonları temsil eder.SortedList
Stack<T>(Last In First Out) Son giren ilk çıkar türündeki obje koleksiyonlarıını temsil eder.Stack

Generics ve CLR Desteği

Generic’ler dil seviyesinde bir özellik değildir, .Net CLR generic’leri otomatik tanır. Oluşturduğunuz generic yapı (sınıf, metod, parametre) sadece bir kez oluşturulur ve belirttiğiniz her tip için CLR arka planda gidip ilk oluşturduğunuz yapıyı kullanarak buna bağlı işlemleri gerçekleştirir. Diğer bir deyişle kaç tane farklı tip kullanarak generic bir yapı kullandığınızın önemi yoktur. Örnek olarak MyList adında bir generic sınıf oluşturalım ve bu sınıfı kullanarak oluşturduğumuz farklı tipler kullanarak oluşturduğumuz instance’ların Microsoft Intermediate Language (MSIL) kodunun sadece ilk oluşturduğumuz yapı olduğunu gözlemleyelim.

//MyList.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace CLRSupportExample
{
    public class MyList<T>
    {
		private static int objCount = 0;

		public MyList()
		{
				objCount++;
		}

		public int Count
		{
				get
				{
					return objCount;
				}
		}
    }
}

//Program.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace CLRSupportExample
{
    class SampleClass {}

		class Program
		{
			static void Main(string[] args)
			{
					MyList<int> myIntList = new MyList<int>();
					MyList<int> myIntList2 = new MyList<int>();

					MyList<double> myDoubleList = new MyList<double>();

					MyList<SampleClass> mySampleList = new MyList<SampleClass>();
	                                
					Console.WriteLine(myIntList.Count);
					Console.WriteLine(myIntList2.Count);
					Console.WriteLine(myDoubleList.Count);
					Console.WriteLine(mySampleList.Count);
					Console.WriteLine(new MyList<SampleClass>().Count);

					Console.ReadLine();
			}
		}
}

MyList adında generic bir sınıf oluşturduk ve <> kullanarak parametrik bir hale getirdik. <> içerisindeki T aslında bizim gerçekte oluşturmak istediğimiz tipi temsil etmektedir. Kullanıcının MyList sınıfını kullanarak kaç intance oluşturduğunu anlamak için MyList sınıfı içerisinde objCount adında statik bir özellik oluşturdum ve objCount değerini generic sınıfın yükleyicisi (constructor)’nde arttırdım. Böylece Count özelliği (property) ile ayni tipte kaç tane instance oluşturuldığunu gözlemleyebiliriz.

Main() metodu içerisinde iki tane MyList<int> tipinde instance, bir tane MyList<double> tipinde instance ve iki tane MyList<SampleClass> tipinde instance oluşturduk. Peki bu durumda Count özellğinin (property) değeri ne olacaktır?

// Program çıktısı
2
2
1
1
2

Program çıktısında ilk iki satırdaki 2 değeri MyList<int> tipi içindir. İlk 1 değeri MyList<double> içindir.İkinci 1 değeri ise MyList<SampleClass> tipi içindir ve bu satırda bir tane SampleClass instance’ı da arka planda oluşmuştur ve bir sonraki 2 değerine sahip olan MyList<SampleClass> instance’ıda oluşan SampleClass’ı kullanmıştır.

Dikkat edileceği üzere MyList<> generic yapısı kullanılarak dört farklı sınıf oluşturulmuştur. Ancak arka tarafta sadece bir tane MyList sınıfı oluşmuş ver her farklı tip için bu sınıf kullanılmıştır. Peki bunu nasıl kanıtlayabiliriz? ildasm.exe aracını kullanarak uygulamayı disassemble ettiğimizde aşağıdaki resimdede görüleceği üzere SampleClass ve MyList sınıflarından birer tane olduğu görülmektedir.

Generic MSIL

Generic Metodlar

Generic sınıflara ek olarak generic metodlarda kullanabilmekteyiz. Generic metodlar herhangi bir sınıfın parçası olabilmekte ve kullanılabilmektedir.

Bir örnek üzerinde inceleyecek olursak :

public class Program
{
    public static void Copy<T>(List<T> source, List<T> destination)
    {
        foreach (T obj in source)
        {
                destination.Add(obj);
        }
    }

    static void Main(string[] args)
    {
        List<int> lst1 = new List<int>();
        lst1.Add(2);
        lst1.Add(4);

        List<int> lst2 = new List<int>();
        Copy(lst1, lst2);
        Console.WriteLine(lst2.Count);
    }
}

Yukarıdaki örneğe baktığımızda Copy() metodunun T tipi ile parametreleştirilmiş bir generic metod olduğu anlaşılmaktadır. Main() içerisinde Copy() metodunu çalıştırdığımızda, derleyici (compiler) metoda parametre olarak geçtiğimiz ilk listedeki öğeleri ikinci listeye ekleyecektir.

Kontrolsüz Tip Parametreleri (Unbounded Type Parameters)

Eğer generic bir yapı veya sınıf oluşturduğunuzda, bu generic yapı yada sınıfın parametre tipinin ne olacağı ile ilgili bir kısıt bulunmamaktadır. Bu durumda da bizim bazı kurallara uymamız gerekir. Örneğin oluşturduğunuz parametric tipin instance’ı ile ==, !=, < gibi operatörleri kullanamazsınız.

if (obj1 == obj2) …

==, !=, < gibi operatörlerin değer (value) tipleri ve referans (reference) tiplere uygulanışı farklı biçimde yapılmaktadır. Eğer bu operatörler oluşturduğunuz generic bir yapıda kısıt olmadan rasgele kullanılmış ise kodun davranışının anlaşılması güç olur ve okunabilirliği azalır. Diğer dikkat edilmesi gereken bir noktada varsayılan yükleyici (default constructor)’dır. Örneğin oluşturduğunuz generic bir yapı için kısıt olarak “new T()” yazduğınızda derleme (compilation) hatası alırsınız, çünkü oluşturacağınız tüm sınıfların parametresiz varsayılan yüklenicisi (constructor) olmayacak ve bazı sınıfların parametreli yüklenicileri olması gerekecektir. Peki oluşturduğunuz generic yapıda ==, !=, < gibi operatörleri ve kısıt uygulamayı istiyorsanız yazının devamında bu işlemlerin nasıl yapıldığını gözlemleyebilirsiniz.

Referans tipleri : Dynamic, Delegate, Interface, Strings, Object, Class. Değer tipleri ise : int , float , double,char vb. tipleridir

Generic Kısıtları (Constraints) ve Faydaları

Generic sınıfı herhangi bir tip belirtmeden sınıf yada metod oluşturmanıza imkan sağlar, sonrasında belirttiğiniz tipe göre sınıf yada metod oluşur ve belirtiğiniz tip üzerinden istediğimiz işlemleri gerçekleştiririz. Böyle bir yapı bize büyük bir esneklik sağlar, bu esnekliğin yanında generic olarak oluştuğumuz yapılara kısıt tanımlayarak bu yapı üzerinde daha fazla kontrol kazanmamıza da olanak tanır.

Örneğin :

public static T Max<T>(T op1, T op2) 
{
   if (op1.CompareTo(op2) < 0)
      return op1;
   return op2;
}

yukarıdaki gibi bir kod derleme (compilation) zamanında “Error 1 ‘T’ does not contain a definition for ‘CompareTo'” hatası verecektir. Çünkü generic metod parametre (argüman) olarak gelen objelerin CompareTo() metodu olduğunu bilmiyor.

Böyle bir durumda CompareTo() metoduna ihtiyacımız olduğunda kısıt tanımlamamız gerekecekti. Yani sadece CompareTo() metodu olan tipler sadece bu metodu kullansın dememiz gerekir. Bunuda sağlayabilmek için kısıtımızı soyut bir interface olan IComparable verdiğimizde bu şartı sağlamış oluruz.

Kısıt tanımlama :

public static T Max<T>(T op1, T op2) where T : IComparable
{
   if (op1.CompareTo(op2) < 0)
      return op1;
   return op2;
}

Yukarıdaki örnekte kısıt tanımlayıp sadece IComparable interface’i ile genişletilmiş objelerin tanımladığımız metod da parametre (argüman) olarak kullanılmasını sağlamış olduk. Bu sayede parametrede gelen objelerin CompareTo() metodu olduğunu biliyoruz ve derlerken aldığımız hatayı düzeltmiş olduk. Sonuç olarak generic metodumuz üzerinde ekstra bir kontrol sağladık ve olası hataları baştan önlemiş olduk.

Generic’lere tanımlanabilecek diğer kısıtlar aşağıdaki gibidir.

where T : struct          T tipi değer (value) tipleri olmalıdır.
where T : class           T tipi referans (referance) tipleri olmalıdır.
where T : new()           T tipi yükleyicisi (constructor) parametresiz olan 
                          bir tip olmalıdır.
where T : class_name      T tipi oluşturduğunuz bir sınıf yada bu sınıfı ile 
                          genişletilmiş alt sınıflar olmalıdır.
where T : interface_name  T tipi belirtilen interface ile genişletilmiş bir 
                          obje olmalıdır.

Referans tipleri : Dynamic, Delegate, Interface, Strings, Object, Class. Değer tipleri ise : int , float , double,char vb. tipleridir

Sadece tek bir kısıt (constraint) tanımlamak yerine ” : where T : IComparable, new()” şeklinde birden çok kısıtta tanımlayabilirsiniz. Böylece IComparable interface’i ile genişletilmiş yükleyicisi (constructor) parametresiz olan nesneler generic ifademizi kullansın diye bir kısıt oluşturmuş oluruz.

Generic’ler ve Kalıtım (Inheritance)

MyClass1<T> şeklinde yazılan parametrik generic sınıflar “open-constructed generic” olarak adlandırılmaktadır. MyClass1<int> şeklinde oluşturulan tipler ise “closed-constructed generic” şeklinde adlandırılmaktadır.

Open-constructed generic bir sınıf ; closed-constructed generic bir tipten türetilebilir (derive) :

public class MyClass2<T> : MyClass1<int>

Open-constructed generic bir tipi aynı tipde generic yapıdan türetilebilir :

 
public class MyClass2<T> : MyClass2<T>

Fakat aşağıdaki gibi farklı yapıdaki generic bir yapıdan türetilemez :

public class MyClass2<T> : MyClass2<Y>

Yukarıdaki gibi bir ifade kullanılamaz. Çünkü Y parametrik bir tiptir.

Generic olmayan sınıflar closed-constructed generic yapıdan türetilebilir, ancak closed-constructed generic yapıdan türetilemez.

 
public class MyClass : MyClass1<int>

Yukarıdaki gibi bir kullanımda MyClass1<int> ifadesi closed-constructed generic yapı olduğundan türetme yapılabilir, ancak aşağıdaki gibi bir kullanım geçersizdir.

 
public class MyClass : MyClass1<T>

Sonuç

Generic’ler .Net 2.0 gelen güçlü özelliklerden biridir. Kod yazarken belirli bir tip belirtmeden tasarladığınız şablonun çalışmasını sağlar. Bu sayede tip güvenlii (type safety) kod yazmamızı ve kod tekrarını önler. Buna ek olarak tasarladığıdığınız generic yapı üzerinde kısıt (constraint) tanımlamanıza imkan tanıyarak genric yapıda kullanacağınız tipleri soyutlayarak daha esnek yapılar tasarlamanıza yardımcı olur.

Kaynak ;

An Introduction to C# Generics
Generics in .NET 2.0

Bir cevap yazın