24 Mayıs 2011 Salı

Generic Kullanımı #2

Generic Kısıtlamaları

C# ta generic-tip parametrelerinin yerine hangi kısıtlamalarda client-tanımlı parametrelerin kullanılacağını derleyiciye belirtmek zorundasınızy. Üç tip kısıtlama vardır. Türeme kısıtlaması derleyiciye generic-tip parametrenin hangi temel tipte class veya interface den türemesi gerektiğini belirtir. Varsayılan yapıcı (default constructor) kısıtlaması derleyiciye generic-tip parametresinin default public contructor a (parametresiz public constructor) sahip olduğunu gösterir. Referans/Değer tip kısıtlayıcısı generic tip parametrenin referans mı yoksa değer tipindemi olduğunu sınırlar. Bir generic tip birden fazla kısıtlayıcı barındırabilir.
Şunu da belirtmek lazım ki bu kısıtlayıcılar opsiyoneldir, ama generic-tip parametreler kullanırken bunları kullanmak mecburi gibidir. Bunları kullanmadığınız sürece object-tip parametrelerde olduğu gibi daha fazla tip-güvenlik kontrolleri yapmanız gerekir.Kısıtlamalar generic-tip metadatanın önemli bir parçasıdır, bu sayede client-side derleyiciside bunun avantajlarından faydalanabilir. Client-side derleyicisi client geliştiricisine kısıtlamalara uymasını zorlayarak tip-güvenliği sağlar.
Kısıtlamaların kullanım ihtiyacına yönelik bir örnek verecek olursak: Bağlı listeye indexleme kabiliyeti ve bir key e göre arama kazandırmak isteyelim

public class LinkedList
{
   T Find(K key)
   {...}
   public T this[K key]
   {
      get{return Find(key);}
   }
}
Bu clienta aşağıdaki kodu yazmasına olanak sağlar.
LinkedList list = new LinkedList();

list.AddHead(123,"AAA");
list.AddHead(456,"BBB");
string item = list[456];
Debug.Assert(item == "BBB");

Arama yapabilmek için tüm listeyi taramak, her bir node un key ini aranan key ile karşılaştırmak ve eşleşen node u geri döndürmek gerekir. Problem şu ki aşağıdaki method derlenmez:

T Find(K key)
{
   Node current = m_Head;
   while(current.NextNode != null)
   {
      if(current.Key == key) //Derlenmez...
         break;
      else
         
         current = current.NextNode;
   }
   return current.Item; 
}
Neden derlenmediğinin sebebi:
if(current.Key == key)
Yukardaki satır derlenmez çünkü derleyici K nin == operatörünü destekleyip desteklemediğini bilemez. Mesela structlar bu çeşit bir gerçekleştirmeyi desteklemez. Bu == operatör kısıtlamasını aşmak için  IComparable arayüzünü kullanabilirsiniz:

public interface IComparable 
{
   int CompareTo(object obj);
}
CompareTo() methodu eşitlik durumunda 0 döndürür, böylece Find() methodu bunu aşağıdaki gibi kullanabilir:

if(current.Key.CompareTo(key) == 0)
Malesef bu kodda derlenmez çünkü derleyici K nın IComparable dan türeyip türemediğini bilme imkanı yoktur.
Siz bunu  IComparable a cast edip kullanabilirsiniz ama bu da tip-güvenliği için pahalıya mal olur:

if(((IComparable)(current.Key)).CompareTo(key) == 0)

Türeme Kısıtlaması

C# 2.0 da kısıtlama tanımlamak için where keyword ünü kullanırız.  where keyword ünü derleyiciye parametrenin neden türemesi gerektiğini belirtmek için kullanırız.

public class LinkedList where K : IComparable
{
   T Find(K key)
   {
      Node current = m_Head;
      while(current.NextNode != null)
      {
         if(current.Key.CompareTo(key) == 0)
            
            break;
         else      
            
            current = current.NextNode;
      }
      return current.Item; 
   }
   //Rest of the implementation 
}
Derleyici burada list in key argümanı için IComparable dan türeme şartı koşucak ve kodu eğer key bundan türemiyorsa derlemiyecektir.
Kısıtlama size IComparable ı kullanmanıza izin verse bile, key olarak değer tipinde, mesela integer tipinde bir değişken kullandığınızda tekrar boxing yapmanız gerekecek. Bu sorunu aşmak için  IComparable kullanmanız gerekecek:

public interface IComparable 
{
   int CompareTo(T other);
   bool Equals(T other);
}
Key için kullanımı aşağıdaki gibidir.

public class LinkedList where K : IComparable
{...}
Kısıtlamalar gerçek türemelerden sonra yazılmalıdır. Mesela LinkedList IEnumerableinterface inden türüyorsa where bundan hemen sonra yer almalıdır:

public class LinkedList : IEnumerable where K : IComparable
{...}
Aynı generic tip parametresinde birden fazla interface kısıtlaması getirebilirsiniz Mesela:

public class LinkedList where K : IComparable,IConvertible
{...}

Her bir generic tip parametre için kısıtlama yazılabilir, mesela:
public class LinkedList where K : IComparable
                             where T : ICloneable 
{...}

Generic tip parametreler için class dan türeme kısıtlamasıda getirebilirsiniz. Ama sadece bir tane belirleyebilirsiniz:
public class MyBaseClass
{...}
public class LinkedList where K : MyBaseClass
{...}

C# kısıtlama olarak başka bir generic tip parametre kullanmanızada olanak sağlar:
public class MyClass where T : U 
{...}

Constructor Kısıtlaması

Generic sınıfın içinde bir generic nesne oluşturmak istediğinizi düşünün. C# derleyicisi clientın kullanacağı tip argümanın tipinde argümanlara sahip bir constructor olup olmadığını bilemez, böylece derleme yapamaz.
Bu problemi aşmak için C# size public default contructor tanımlamanıza olanak tanır. Bu new()kısıtlaması kullanarak yapılır. Örneğin:

class Node where T : new() 
{
   public K Key;
   public T Item;
   public Node NextNode;
   public Node()
   {
      Key      = default(K);
      Item     = new T();
      NextNode = null;
   }
}
Türeme kısıtlaması ile birlikte kullanımı aşağıdaki gibidir:

public class LinkedList where K : IComparable,new() 
{...}

Referans/Değer Tip Kısıtlaması

Generic tip parametresinin değer tipindemi yoksa başka tiptemi olacağını aşağıdaki gibi kısıtlayabilirsiniz:

public class MyClass where T : struct 
{...}
Aynı şekilde referans tipinde olduğunuda class keywordü ile kısıtlayabilirsiniz:

public class MyClass where T : class 
{...}

Hiç yorum yok: