Generic Türler (Generic Types) -II

.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
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 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
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 (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
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 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
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

Generic Türler (Generic Types) -II” hakkında 4 yorum

  1. ibrahim özbey

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

    Cevapla

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir