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


1 + = 10