Generic Türler (Generic Types) -II

C#, VB.NET, ASP.NET Add comments

.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# (VB.NET(Of T)) ile temsil edilen tip parametleri alarak sadece o tipte işlem yapmasını sağlar. Yani MyListe şeklinde tanımlanmış bir classı, programcılar, herhangi bir casting, boxing, unboxing işlemi yapmaksızın MyListe, MyListe veya MyListe şeklinde kullanabilir.

.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 sınıfının basit kullanımı gösterilmiştir;

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 list1 = new List();
list1.Add(2006); // Casting veya boxing işlemi yok
list1.Add("Bugün hava çok güzel"); // Compiletime'da hata verir

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 {

    T t1;

    public T Deger {
        get {
            return t1;
        }
        set {
            t1 = value;
        }
    }//Deger

}//Liste

Buradaki ifadesi, type parameter olarak görev yapar. Liste sınıfına herhangi bir veri türünde parametre girebiliriz.

'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 oStr = new Liste();
//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
Liste oInt = new Liste();
oInt.Deger = 2006;
Console.WriteLine(oInt.Deger);
Console.WriteLine(oInt.Deger.GetType());

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(ref a, ref b);
    Console.WriteLine(a);//2
    Console.WriteLine(b);//1

    string x = "Ahmet";
    string y = "Kaymaz";
    Swap(ref x, ref y);
    Console.WriteLine(x);//Kaymaz
    Console.WriteLine(y);//Ahmet
}//Main

static void Swap(ref T left, ref T right)
{
    T temp;
    temp = left;
    left = right;
    right = temp;
}//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(int size)
{
    return new T[size];
}

//Metodu kullanalım
string[] Arr = DiziOlustur(7);

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 Stck = new Stack();
    Stck.Push("Ali");
    Stck.Push("Veli");
    GenMethod(Stck);
}//Main   

static void GenMethod(Stack stack)
{
    foreach (string Str in stack)
    {
        Console.WriteLine(Str);
    }
}//GenMethod

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 (VB.NET –> List(Of T)) sınıfını kullanacağız.

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 Kisiler= new List();
Kisiler.Add(new Kisi("Ali",25));
Kisiler.Add(new Kisi("Ayşe", 21));
Kisiler.Add(new Kisi("Veli", 23));

//foreach yöntemi
foreach (Kisi oKisi in Kisiler)
{
    Console.WriteLine(oKisi.AdSoyad + " " " + oKisi.Yas);
}
Console.WriteLine();

//for yöntemi
for (byte x = 0; x < Kisiler.Count;x++)
    Console.WriteLine(Kisiler[x].AdSoyad + " " " + Kisiler[x].Yas);

    Console.ReadLine();
}//Main

public class Kisi
{
protected string _adsoyad;
protected int _yas;

public Kisi(string AdSoyad,int Yas) {
    _adsoyad = AdSoyad;
    _yas = Yas;
}

public string AdSoyad {
    get {
	return _adsoyad;
    }
    set{
	_adsoyad = value;
    }
}

public int Yas {
    get {
	return _yas;
    }
    set{
	_yas = value;
    }
}

}//Kisi

}//Program

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.BirSeyYap(10);
    }//Main   

    class MyCls
    {
        private static T t1;
        public static void BirSeyYap(T _t) {
            t1 = _t;
            Console.WriteLine(t1.ToString());//10 yazdırır
        }
    }//MyCls

}//Program

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 o1 = new DisCls();
DisCls.IcCls o2 = new DisCls.IcCls();
DisCls.IcCls o3 = new DisCls.IcCls();
}//Main   

class DisCls
{
 public DisCls() {
    Console.WriteLine("Dış class constructör");
}

public class IcCls{
    public IcCls()
    {
	Console.WriteLine("İç class constructör");
    }
}//IcCls
}//DisCls
}//Program

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 " where:" yapısı VB.NET tarafında "Of As " formatı kullanılır.

'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 where T : struct { }
//Class tipindeki parametreleri kabul eder
public class MyCollection2 where T : class { }

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 MyList1 = new MyCollection1();
//int, referans türü olmadığı için hata verir
MyCollection2 MyList2 = new MyCollection2();

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
    where T : class
    where V : struct{
    // . . .
}

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 where K : IComparable { }

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 where V : T
    {
    }
    static void Main(){
        GenCls o1 = new GenCls();//Doğru
        //GenCls o2 = new GenCls();//Yanlış
        //The type 'Program.C1' must be convertible to 'Program.C2' in order to use it as parameter 'V' in the generic type or method 'Program.GenCls'
        Console.ReadLine();
    }//Main

}//Program

.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 where K : new() { }

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();

Console.ReadLine();
}//Main   

public class C1
{
public void Method1() where T : C2, new()
{
    Console.WriteLine("C1.Method1");
    T o1 = new T();
    o1.Method2();
}
}//C1

public class C2
{
public void Method2()
{
    Console.WriteLine("C2.Method2");
}
}//C2

}//Program

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 o1 = new MyCls();
MyCls o2 = new MyCls();
}//Main   

class MyCls
{
static MyCls() {
    if (typeof(T) == typeof(int) )
	Console.WriteLine(typeof(T));
}//MyCls
}//MyCls

}//Program

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()
    {
        //return null; Değer türleri için hatalı
        //return 0; Referans türleri için hatalı
    }
}

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()
    {
        return default(T);
    }
}

Basit bir örnekle bu operatörün testini yapalım.

class Program
{
static void Main(){
MyGenCls o1 = new MyGenCls();
if (o1.Obj == null)
    Console.WriteLine("o1.Obj = null");

MyGenCls o2 = new MyGenCls();
if (o2.Obj == 0)
    Console.WriteLine("o2.Obj = 0");

}//Main   

class MyGenCls
{
public T Obj;

public MyGenCls()
{
    Obj = default(T);
}

}//MyGenCls

}//Program

o1.Obj = null
o2.Obj = 0

4 Responses to “Generic Türler (Generic Types) -II”

  1. Aytaç Kargınoğlu Says:

    Hocam, bir konu ancak bu kadar güzel anlatılabilir emeğine sağlık

  2. ibrahim özbey Says:

    hocam gerçekten süper olmuş. anlatım sade,kısa ama içinde gerekli tüm bilgi var

  3. Latif OZTURK Says:

    Gercekten konu cok guzel anlatilmis. Tesekkurler.

  4. mustafa Türkyaşar Says:

    gecekten mukemmel bir anlatım

Leave a Reply