.NET 2.0 ile birlikte gelmiş en güçlü özelliklerinden biri hiç şüphesiz C++’taki template konseptiyle benzerlik gösteren generic yapısının destekleniyor olmasıdır. .NET uygulamalarında değer ve referans olmak üzere iki tür veri türü kullanılacağını biliyoruz. Bazı durumlarda aynı değişkende hem değer hem de referans türünde veriler barındırmak isteyebiliriz. Bu durumda Framework’teki bütün veri türlerinin türediği System.Object türü kullanılır. Bütün veri türleri, temelde object türünde olduğu için bir değişkenin hem değer hem de referans değerleri koruması için değişkenin türünü object olarak set ederiz. Bu yöntem, çoğu zaman kullanışlı görünse de beraberinde performans kaybını getirir. Bir programda iyi performans sağlamanın bir yolu da geçici olarak tuttuğumuz verileri memory üzerinde en doğru tiplerle barındırmaktır. Yani integer olarak strack üzerinde korumamız gereken “456¨ değerini object türünde heap üzerinde depolamamız mantıklı bir çalışma olmaz. Ve en önemlisi object türüne değer atarken veya bu türden değer okurken, CLR değerini gerçek tipini yakalayarak boxing, unboxing işlemleri yapar. Bu da CPU bacağında ek yük oluşturmaktadır.
Bu şekilde çalışan veri türlerinin başında koleksiyon sınıfları gelmektedir. Önceki bölümlerde işlediğimiz koleksiyon nesneleri, elemanları doğrudan object türünde barındırır. Sadece numeric değerleri tutmak istesek bile bu değerler koleksiyonlar tarafından object olarak tutulur. Bu değerleri, başka işlemlerde kullanmak üzere okumak istediğimiz casting işlemi yaptırmak gerekir. Oysa veriler, koleksiyon içerisinde oldukları gibi yani integer türünde durmuş olsalarda boxing, unboxing, casting gibi performans kaybına neden olan işlemler olmayacaktı. .NET 2.0 ile birlikte bu sorun, Generic denilen mimariyle giderilmiş oldu.
Generic yapıyı örneklendirmeden önce generic olmayan bir metod üzerinde çalışalım.
Module Module1 Sub Main() Dim oInt As New NonGeneric(2006) oInt.TipGoster() Dim vInt As Integer = CInt(oInt.GetObj()) Console.WriteLine(("Değeri : " & vInt)) Console.WriteLine() Dim oStr As New NonGeneric("Bugün hava güzel.") oStr.TipGoster() Dim vStr As String = CStr(oStr.GetObj()) Console.WriteLine("Değeri : " & vStr) Console.ReadLine() End Sub Class NonGeneric Private obj As Object Public Sub New(ByVal o As Object) obj = o End Sub Public Function GetObj() As Object Return obj End Function Public Sub TipGoster() Console.WriteLine("Obj'nin veri tipi : " & obj.[GetType]()) End Sub End Class 'NonGeneric End Module
class Program { static void Main(){ NonGeneric oInt = new NonGeneric(2006); oInt.TipGoster(); int vInt = (int)oInt.GetObj(); Console.WriteLine("Değeri : " + vInt); Console.WriteLine(); NonGeneric oStr = new NonGeneric("Bugün hava çok güzel."); oStr.TipGoster(); String vStr = (string)oStr.GetObj(); Console.WriteLine("Değeri : " + vStr); Console.ReadLine(); }//Main }//Program class NonGeneric { object obj; public NonGeneric(object o) { obj = o; } public object GetObj() { return obj; } public void TipGoster() { Console.WriteLine("Obj'nin veri tipi : " + obj.GetType()); } }//NonGeneric
Obj'nin veri tipi : System.Int32
Değeri : 2006
Obj'nin veri tipi : System.String
Değeri : Bugün hava güzel.
Burada görüldüğü gibi NonGeneric sınıfının constructörü, object türünde değer alıyor ve GetObj() metodu da bu değeri yine object türünde döndürmektedir. Burada iki temel performans sorunumuz var; birincisi, GetObj() metodunun döndürdüğü değeri Main programında kullandığım zaman casting, dönüştürme işlemi yapıyoruz. İkinci sıkıntı ise, .NET’in özellikle üzerinde durduğu tip güvenliği(type safety) konusudur. Örnekte GetObj() metoduyla aldığımız değeri, kendisiyle uyuşmayan bir veri türüne dönüştürme riskimiz olduğu için runtime’da “invalid cast exception” hatası alma ihtimalimiz bulunmaktadır. Oysa bu tür hataları, compiletime’da yakalamamız gerekir. Bu iki sıkıntıyı gidermek için generic mimarisi kullanılır. Generic mimarisi, interface, class, method, delegate gibi yapıların C#
.NET 2.0, klasik koleksiyon sınıflarının generic yapıda kullanılması için yeni versiyonlarını sunar. System.Collections.Generic kütüphanesinde bulunan bu sınıflar daha önce gördüğümüz klasik koleksiyon nesneleri gibi kullanılır. Aşağıdaki tabloda .NET 2.0’daki System.Collections.Generic.List
Dim list1 As List(Of Integer) list1.Add(2006) 'Casting veya boxing işlemi yok list1.Add("Bugün hava çok güzel") 'Compiletime'da hata verir
List
Net 2.0′ da class, struct, interface, method, collection ve delegate yapılarını generic olarak oluşturabilir ve kullanabiliriz. Şimdi bunları örneklendirelim.
Generic Class ve Method
.NET 2.0 ortamında generic mimarisini destekleyen class ve metodlar tanımlayabiliriz. Basit bir generic classı şu şekilde kodlayabiliriz;
Class Liste(Of T) Private t1 As T Public Property Deger() As T Get Return t1 End Get Set(ByVal value As T) t1 = value End Set End Property End Class
class Liste
Buradaki
'Liste generic classının string versiyonu Dim oStr As New Liste(Of String)() 'Değerini girelim oStr.Deger = "Bugün hava çok güzel." 'Değişkenin değerini yazdıralım Console.WriteLine(oStr.Deger) 'Değişkenin tipini yazdıralım Console.WriteLine(oStr.Deger.[GetType]()) Console.WriteLine() 'Liste generic classının integer versiyonu Dim oInt As New Liste(Of Integer)() oInt.Deger = 2006 Console.WriteLine(oInt.Deger) Console.WriteLine(oInt.Deger.[GetType]())
//Liste generic classının string versiyonu
Liste
Bugün hava çok güzel.
System.String
2006
System.Int32
Aynı şekilde generic mimarisine uygun bir metod da tanımlanabilir. Aşağıda, Swap isminde bir metod tanımlanmış ve Main() metodu içerisinde hem integer hem de string türü için çağrılmıştır.
Sub Main() Dim a As Integer = 1 Dim b As Integer = 2 Swap(Of Integer)(a, b) Console.WriteLine(a) '2 Console.WriteLine(b) '1 Dim x As String = "Ahmet" Dim y As String = "Kaymaz" Swap(Of String)(x, y) Console.WriteLine(x) 'Kaymaz Console.WriteLine(y) 'Ahmet End Sub Sub Swap(Of T)(ByRef left As T, ByRef right As T) Dim temp As T temp = left left = right right = temp End Sub
static void Main(){
int a = 1;
int b = 2;
Swap
Generic yapılan güçlü bir yanı da, metodların alacağı veya geriye döndüreceği veri tipinin de parametrik yapılabiliyor olmasıdır. Aşağıdaki DiziOlustur() metodu, geriye paramatre olarak aldığı tipte bir dizi döndürür.
Public Function DiziOlustur(Of T)(ByVal size As Integer) As T() Return New T(size - 1) {} End Function 'Metodu kullanalım Dim Arr As String() = DiziOlustur(Of String)(7)
public static T[] DiziOlustur
Aşağıdaki GenMethod() metodu, parametre olarak generic şablonunu destekleyen stack nesnesini almaktadır.
Sub Main() Dim Stck As New Stack(Of String)() Stck.Push("Ali") Stck.Push("Veli") GenMethod(Stck) End Sub Sub GenMethod(ByVal stack As Stack(Of String)) For Each Str As String In stack Console.WriteLine(Str) Next End Sub
static void Main(){
Stack
Veli
Ali
PÜF : Programların başlangıç noktası olan Main() metodu, generic bir class içerisinde bulunamaz.
Şimdiye kadar yaptığımız örneklerde basit olsun diye değer türlerini tercih ettik. Generic yapıdaki üyelere referans türlerini de parametre olarak geçebiliriz. Koleksiyon bölümünde yaptığımız örneklere benzer bir örnek yapalım. .NET 2.0 ile birlikte gelmiş olan List
Imports System Imports System.Collections.Generic Module Module1 Sub Main() Dim Kisiler As New List(Of Kisi)() Kisiler.Add(New Kisi("Ali", 25)) Kisiler.Add(New Kisi("Ayşe", 21)) Kisiler.Add(New Kisi("Veli", 23)) 'foreach yöntemi For Each oKisi As Kisi In Kisiler Console.WriteLine(oKisi.AdSoyad & " " " & oKisi.Yas) Next Console.WriteLine() 'for yöntemi For x As Byte = 0 To Kisiler.Count - 1 Console.WriteLine(Kisiler(x).AdSoyad & " " " & Kisiler(x).Yas) Next Console.ReadLine() End Sub Public Class Kisi Private _adsoyad As String Private _yas As Integer Public Sub New(ByVal AdSoyad As String, ByVal Yas As Integer) _adsoyad = AdSoyad _yas = Yas End Sub Public Property AdSoyad() As String Get Return _adsoyad End Get Set(ByVal value As String) _adsoyad = value End Set End Property Public Property Yas() As Integer Get Return _yas End Get Set(ByVal value As Integer) _yas = value End Set End Property End Class End Module
using System;
using System.Collections.Generic;
class Program
{
static void Main(){
List
Ali » 25
Ayşe » 21
Veli » 23
Ali » 25
Ayşe » 21
Veli » 23
Generic şablonlu yapılarda normal classlar gibi property, constructor, static elemanlar, methodlar gibi üyeleri rahatlıkla oluşturabiliriz. Aşağıdaki tabloda generic yapılı MyCls sınıfında static üyeler kullanılmıştır;
Module Module1 Sub Main() MyCls(Of Integer).BirSeyYap(10) End Sub Private Class MyCls(Of T) Private Shared t1 As T Public Shared Sub BirSeyYap(ByVal _t As T) t1 = _t Console.WriteLine(t1) '10 yazdırır End Sub End Class End Module
class Program
{
static void Main(){
MyCls
Generic şablonlarda iç içe generic yapılar da kullanılabilir. Nested generic type olarak isimlendirilen bu durumda iç sınıfların pathiyle birlikte veri tipi de argüman olarak yollanır.
Module Module1 Sub Main() Dim o1 As New DisCls(Of String)() Dim o2 As New DisCls(Of String).IcCls(Of Integer)() Dim o3 As New DisCls(Of Integer).IcCls(Of Integer)() End Sub Class DisCls(Of U) Sub New() Console.WriteLine("Dış class constructör") End Sub Public Class IcCls(Of V) Sub New() Console.WriteLine("İç class constructör") End Sub End Class End Class End Module
class Program
{
static void Main(){
DisCls
Dış class constructör
İç class constructör
İç class constructör
Generic Mimarisinde Constraint Kullanımı
Generic yapısının önemli bileşenlerinden biri de constraintlerdir. Bir constraint, generic yapısının hangi tür veri tiplerini kabul edeceğini bildirir. Bazı durumlarda sadece değer veya referans türünde parametre girilmesini(Reference\Value type constraint), belli sınıf ve interface’den inherit edilmiş olmasını(Derivation constraint) şart koşabiliriz. Bu şekilde tip parametreleri üzerinde kısıtlamalar belirtmek için C# tarafında “
'Structure tipindeki parametreleri kabul eder Public Class MyCollection1(Of T As Structure) End Class 'Class tipindeki parametreleri kabul eder Public Class MyCollection2(Of T As Class) End Class
//Structure tipindeki parametreleri kabul eder
public class MyCollection1
Bu tablodaki MyCollection1 koleksiyonu, argüman olarak yalnızca değer türünde(value type) olan tipleri kabul eder. Aynı şekilde MyCollection2 koleksiyonu da yalnızca class, interface, delegate, array gibi referans türünde(reference type) olan tipleri kabul eder. Bu durumda aşağıdaki kullanımlar hataya neden olacaktır;
'String, değer türü olmadığı için hata verir Dim MyList1 As MyCollection1(Of String) 'Integer, referans türü olmadığı için hata verir Dim MyList2 As MyCollection2(Of Integer)
//string, değer türü olmadığı için hata verir
MyCollection1
Tanımladığımız classa birden fazla argümanı gönderdiğimizde her argümana özel constraint de tanımlayabiliriz.
Class C1(Of T As Class, U, V As Structure) ' . . . End Class
class C1
Bir koleksiyonu tanımlarken parametre alacağı tipi belli sınıf veya arayüzleri uygulamış olmasını şart koşabiliriz.
Public Class Dictionary(Of K As IComparable, V) End Class
public class Dictionary
Bu tanımlama, K tipinin, IComparable arabirimini implement etmesi gerektiğini bildirir. Eğer K’ya parametre olarak gönderdiğimiz veri tipi, IComparable arabirimini implement etmemişse derleme aşamasında hata alırız. Bu ifadeyi kullanarak birinci durumdaki gibi tüm referans türlerini değil sadece ismini verdiğimiz türü(örneğin MyClass isminde bir sınıf) kabul etmesini sağlayabiliriz. Aşağıdaki örnekte de birbirleriyle ilişkili iki parametrenin kullanımı gösterilmiştir.
Module Module1 Private Class C1 End Class Private Class C2 Inherits C1 End Class 'V parametresi, T tipini inherit etmiş olmalıdır Private Class GenCls(Of T, V As T) End Class '========Main() metodu================ Sub Main() Dim o1 As New GenCls(Of C1, C2)() 'Doğru 'Dim o2 As New GenCls(Of C2, C1)() 'Yanlış 'ConsoleApplication1.Module1.C1' does not inherit from or implement the constraint type 'ConsoleApplication1.Module1.C2'. End Sub End Module
class Program
{
class C1
{
}
class C2 : C1
{
}
//V parametresi, T tipini inherit etmiş olmalıdır
class GenCls
.NET 2.0’da kullanılan diğer constraint türü de constructor constraint olarak tanımlanan kısıtlamadır. Bu kısıtlama, parametre olarak kullanılacak türün mutlaka default constructor(parameterless constructor-parametresiz yapılandırıcı) metoduna sahip olmasını şart koşar.
Public Class MyCls(Of K As New) End Class
public class MyCls
Bu ifade, K’ya parametre olarak gönderilecek tipin, default constructör içermesi gerektiğini bildirir. Aşağıdaki örneği inceleyelim;
Module Module1 Sub Main() Dim obj As New C1() obj.Method1(Of C2)() Console.ReadLine() End Sub Public Class C1 Public Sub Method1(Of T As {C2, New})() Console.WriteLine("C1.Method1") Dim o1 As New T() o1.Method2() End Sub End Class Public Class C2 Public Sub Method2() Console.WriteLine("C2.Method2") End Sub End Class End Module
class Program
{
static void Main(){
C1 obj = new C1();
obj.Method1
C1.Method1
C2.Method2
Default constructor kısıtlaması, diğer kısıtlamalarla birlikte kullanıldığında en son belirtilmesi gerekir.
Generic yapılarda constructör üye de tanımlayabiliriz. Constructör tanımlarken classtan her instance oluşturulurken bu constructörün çalışacağını gözönünde bulundurmalıyız. Doğrudan constructörün parametre yapısında bir tip tanımlaması yapamadığımız için metodun içerisinde kendimiz bir constraint belirtmeliyiz. Aşağıdaki örnekte constructör içinde argüman olarak gönderilmiş tipe göre yönlendirme yapılmıştır.
Module Module1 Sub Main() Dim o1 As New MyCls(Of Integer)() Dim o2 As New MyCls(Of String)() End Sub Class MyCls(Of T) Shared Sub New() If GetType(T) Is GetType(Integer) Then Console.WriteLine(GetType(T)) End If End Sub End Class End Module
class Program
{
static void Main(){
MyCls
System.Int32
C# default() opearatörü
Generic yapıları tanımlarken bazen argüman olarak kullandığımız tipin default değerine ihtiyacımız olur. Fakat parametre olarak hem değer türü hem de referans türü gelebileceği için her ikisine uygun default değer döndürmeliyiz. Eğer null değer döndürürsek değer türü için sorun çıkar, eğer 0 değeri döndürürsek referans türü için sorun çıkarır. Kendisine parametre olarak gönderilmiş tipin default değerini döndüren bir metod yazdığımızı düşünelim. Bu durumda hangi değeri döndüreceğiz?
class MyGenClass
{
public T GetDefaultValue
Bu metodun compiletime’da hata vermemesi ve runtime’da doğru sonuçlar vermesi için default() operatörü kullanılır. Bu operatör, parametre olarak aldığı tipin varsayılan değerini döndürür. Eğer tip, değer türündeyse geriye o türün default değeri(numerik ise 0, bool ise false), referans türündeyse geriye null değeri döner.
class MyCls
{
public T GetDefaultValue
Basit bir örnekle bu operatörün testini yapalım.
class Program
{
static void Main(){
MyGenCls
o1.Obj = null
o2.Obj = 0
Hocam, bir konu ancak bu kadar güzel anlatılabilir emeğine sağlık
hocam gerçekten süper olmuş. anlatım sade,kısa ama içinde gerekli tüm bilgi var
Gercekten konu cok guzel anlatilmis. Tesekkurler.
gecekten mukemmel bir anlatım