Home > Delphi > Enine Boyuna Generics – Bölüm 2

Enine Boyuna Generics – Bölüm 2

Posted on 23 Eylül 2007 | 1 Yorum

Bir önceki bölümde Generics hakkında bazı terminolojiler üzerinde durduk. Ayrıca ilk başta verdiğimiz örnek ile, bir sınıfı veya bir metodu object yaklaşımı ile nasıl genlleştirilebileceğini gördük. Önceki makaleyi okuduktan sonra objcet yaklaşımı ile Generics arasındaki farkı idrak ettiğinize inanıyorum. Ve Generics’in avantajlarını ve kolaylığını anladığınızı düşünüyorum.

Bu bölümde Sınırlandırıcıları(Constraints) göstermeye çalışacağız. Hazırsanız buyrun.

Sınırlandırıcılar(Kısıtlamalar, Constraints)

Öyle olurki bazen, tip argümanlarınızın belli metodları ve özellikleri içermesini isteyebilrsiniz. Mesela çizim yapma özelliği olan sınıfların kolleksiyonunu tutan bir generic sınıf düşünün. İsmi de TCizebilenKolleksiyon gibi bir şey olsun. Bu generic kolleksiyona atayacağınız her elemanın bir çizim gerçekleştiren “Ciz” gibi bir metodunun olmasını isteyebilirsiniz. Bu durumda, tip parametresini belli arayüzler ile sınırlandırmanız gerekmektedir. Bu örneğimizi koda dökecek olursak, arayüzümüz şöyle bir şey olacak:

ICizebilen = interface
public
  procedure Ciz;
end;

Bu arayüz ile sınırlandırılmış olan kolleksiyon sınıfımız da şöyle bir şey olabilir:

TCizebilenKolleksiyon<T: ICizebilen> = class
...
end;

Bu durumda T parametresine atayacağımız her argümanın ICizebilen arayüzünün sahip olduğu özellik ve metodlara sahip olacağını garantilemiş oluyoruz.

.Net ortamında kullanabilceğiniz bir çok arayüz bulunmaktadır. Bu arayüzler genelde “able” son eki ile biterler. “able” ekinin Türkçe karşılığı “-ebilen”, “-abilen” olarak düşünebilirsiniz.. ISerializable, IComparable, ICloneable, vs…

Tip parametreleri birden fazla arayüz sınırlandırıcısı alabilir. Bu durumda tip parametreleri virgül yerine aynen prosedür ve fonksiyonların parametrelerinde olduğu gibi “;” noktalı virgül ile ayrılırlar. Mesela:

TSinif<T: ISerializable; U: IColoneable> = class

gibi… Bu örneğe göre T parametresi ISerializable arayüzünü, U parametresi de IColoneable arayüzünü desteklemesi şarttır.

Ayrıca, birden fazla sınırlandırıcı, tek bir tip parametresine atanabilir. Bu durumda bunlar, “AND” lojik mantığına göre tip parametresini sınırlandırırlar.

TSinif<T: ISerializable, IColoneable> = class

gibi… Bu örnekte T parametresi hem ISerializable, hem de IColoneable arayüzlerini desteklemek zorundadır. Aynı şekilde prosedür ve fonksiyon parametrelerinde olduğu gibi aynı sınırlandırmaya sahip iki tip parametresini virgül ile ayrırak beraber yazabilirsiniz:

TSinif<T, U: ISerializable> = class

gibi… Bu örneğe sınırlandırıcıya sahip olmayan bir parametre daha ekleseydik noktalı virgül ile ayırmamız gerekecekti:

TSinif<T, U: ISerializable; Z> = class

gibi…

Bunlar grammer ile ilgili kısımlardı. Peki derleyicinin davranışı nasıl olacak? Aşağıdaki örneğe bakalım:

TPrintableCollect<T: IPrintable> = class ...
...
TDeneme = class(IPrintable)
  procedure Print;
end;

THata = class
end;
...
var
  Calisan: TPrintableCollect<TDeneme>;
  Hatali: TPrintableCollect<THata>; //Syntax hatası: THata sınıfı IPrintable arayüzünü desteklemiyor.

Gördüğünüzü gibi THata sınıfı IPrintable arayüzüne yabancı olduğu için tip argümanı olarak atadığımızda syntax hatası alıyoruz.

Bununla beraber aynı mantık ile arayüz yerine bir sınıfı sınırlandırıcı olarak kullanabilirsiniz. Bu durumda generic sınıfımız ancak ve ancak sınırlayıcı olarak verdiğimiz sınıf ve alt sınıflarından tipleri alabilir. Mesela:

TBirSinif = class...
TBirAltSinif = class(TBirSinif)...
TBirKolleksiyon<T: TBirSinif> = class...

Bu örneğimizde T tip parametresine ancak ve ancak TBirSinif ve ondan türeyen sınıflar argüman olarak atanabilir.

Sınırlandırıcılarda Belirsizlik

İki adet sınırlandırıcı, bir tip parametresine atandığını düşünelim. Mesela:

TTest<T: ISerializable, ICloneable> = class
  BirNesne: T;
  procedure Klonla;
end;

procedure TTest<T>.Klonla;
begin
  BirNesne.Clone;
end;

Derleyici “BirNesne” değişkeninin ISerializable ve ICloneable arayüzlerini desteklediğini biliyor. Ayrıca IClonable arayüzünde de Clone isminde bir metod olduğunu da biliyor. Bu yüzden Klonla metodunda Clone metodunu çağırırken herhangi bir çevirim işlemi yapmadık. Çünkü derleyici, Clone metodunun hangi arayüze ait olduğunu biliyor.

Farz edelim ki Clone metodu, hem ISerializable hem de ICloneable arayüzlerinde bulunuyor olsun. Bu durumda Clone metodunu yukarıdaki gibi çağırdığımızda ne olacak? Derleyici bu durumda belirsizilik olduğuna dair bir hata mesajı gösterecektir. Bu belirsizliği ortadan kaldırmak için harici çevirim(explicit casting) yapmalıyız. Yani:

procedure TTest<T>.Klonla;
begin
  ISerializable(BirNesne).Clone;
  ICloneable(BirNesne).Clone;
end;

Bu durumda, ilk Clone çağırımı ISerializable arayüzü üzerinden, ikincisi ise ICloneable arayüzü üzerinden gerçekleşecektir.

Sınırlandırıcı Olarak constructor, class ve record Kullanmak

Eğer sınırlandırıcı olarak record gibi referansız tipler kullanacaksak bu durumda “record” ayrılmış kelimesini kullanıyoruz. Ama dikkat etmemiz gereken konu kullanacağımız tip argümanı nullable yani null değer alabilen bir tipten olmamalı. Mesela string nullable’dır ama Integer değildir.

Aynı şekilde eğer sınırlandırıcımız sadece referanslı tipler olacaksa bu sefer de “class” ayrılmış kelimesini kullanmaktayız. Ayrıca kullanacağımız tip nullable olabilmeli. Yani tip argümanı olarak sınıfların yaninda string gibi nullable tipleri de kullanabiliriz. Bunları şu şekilde örneklendirelim:

TReferansiz<T: record> = class...
TReferansli<T: class> = class...
TBirRecord = record
end;

var
  Hatali1: TReferansiz<string>; //Hatalı, çünkü string nullable,
  Hatali2: TReferansli<Integer>; //Hatalı, Integer referanslı bir tip değil
  Uygun1: TReferansiz<TBirRecord>;
  Uygun2: TReferansiz<Integer>;
  Uygun3: TReferansli<string>; //string nullable bu yüzden uygun.

gibi…

Constructor kelimse ise, sınırlandırıcıda kullanılan sınıfın mutlaka parametresiz bir constructor’a sahip olmasını şart koşmaktadır. Mesela:

TBirSinif = class
  constructor Create;
end;

TBirBaskaSinif = class

end;

TBirKolleksiyon<T: constructor> = class...
...

var
  Uygun: TBirKolleksiyon<TBirSinif>;
  Hatali: TBirKolleksiyon<TBirBaskaSinif>;

Bu örnekte Uygun nesnesinde tip argümanı olarak TBirSinif tipini girdik. Ve derleyici bunu kabul etti. Çünkü TBirSinif parametresiz bir constructor’a sahip ve sınırlandırıcımız için uygun. Fakat Hatali nesnesinde tip argümanımıza girilen TBirBaskaSinif tipi parametresiz bir constructor’a sahip değil. Bu yüzden derleyici burada hata gösterecektir.

Sonuç

Normalde Delphi.NET derleyicisi, generics ve .net 2.0′a tam destek vermekle beraber, bir çok bug da içeriyor. Gerçi bu cümleleri yazdığım sıralarda, RAD Studio 2007 daha 1 haftadır piyasada. Şu an bildiğim kadarı ile Rad Studio 2007′in çıkacak olan update’inde büyük çoğunlukla derleyici ve asp.net’deki düzeltmeler bulunacak. Derleyici düzeltmelerinde de pastanın büyük payını generics ile ilgili hatalar alacak. Bu yüzden generics ile uğraşırken “Internal Error XXXX” gibi hatalar ile karşılaşırsanız kafanızı duvarlara vurmayın. Çünkü internal error’lerin hepsi bug olarak kabul edilir. Böyle bir durumla karşılaştığınızda eğer yoksa QC’ye rapor edebilirsiniz.

Fatih Tolga Ata © 2007

Kaynaklar:

  • CodeGear’ın Delphi Parametrize Tipler ile İlgili Taslak Metinleri
  • .NET 2.0 SDK Yardım Dosyası
  • Microsoft Visual C# 2005 Step by Step, John SHARP, 2005 Edition

» Tags: , , , , ,

1 Yorum

(Required)
(Required, will not be published)

Switch to our mobile site