Tag Archives: virtual

C#’ta Fonksiyon Üyeleri

C#’ta çalıştırılabilir kod içeren üyeler genellikle sınıfın fonksiyon üyeleri olarak adlandırılırlar. Bir önceki yazıda belirttiğim konu olan methodlar konusu da aslında bir çeşit fonksiyon üyesidir. Aşağıda tablo Liste<T> olarak tanımlanmış bir generic sınıfın bir çok fonksiyon üyesi örneğini gösterir:

public class Liste<T> {
const int varsayılanKapasite = 4; Sabit
T[ ] elemanlar;
int sayaç;
Alanlar
public Liste (int kapasite = varsayılanKapasite ) {
elemanlar = new T[kapasite];
}
Yapıcı
public int Sayma {
get { return sayma; }
}
public int Kapasite {
get {
return elemanlar.Length;
}
set {
if (value < sayma) value = sayma;
if (value != elemanlar.Length) {
T[] yenielemanlar= new T[value];
Array.Copy(elemanlar, 0, yenielemanlar, 0, sayma);
elemanlar = yenielemanlar;
}
}
}
Özellikler
public T this[int index] {
get {
return elemanlar[index];
}
set {
elemanlar[index] = value;
OnChanged();
}
}
İndexleyiciler
public void Add(T item) {
if (sayma == kapasite) kapasite = sayma * 2;
elemanlar[sayma] = item;
sayma++;
OnChanged();
}
Methodlar
public event EventHandler Changed; Olaylar (Events)
public static bool operator ==(Liste<T> a, Liste<T> b) {
return Equals(a, b);
}
public static bool operator !=(Liset<T> a, Liste<T> b) {
return !Equals(a, b);
}
Operatörler
}
  • Yapıcılar (Constructors)

C# hem örnek hem de static yapıcıları destekler. Örnek yapıcılar, sınıfın ilk kullanımına hazırlamak için gerekli olan uygulama üyeleridir. Static yapıcılar, bir eylem gereken örnek sınıfın ilk yüklendiği zaman gerekli olan uygulama üyeleridir.

Bir yapıcı aynı bir method gibi tanımlanır fakat geri dönüş tipi olmamalı ve içerdiği sınıf ile aynı isme sahip olmalıdır. Eğer bir yapıcı, static anahtar kelimesi ile tanımlanmışsa o static yapıcıdır, aksi taktirde örnek yapıcı olarak adlandırılırlar.

Örnek yapıcılar aşırı yüklenebilir. Örneğin, Liste<T> adında bir sınıfın iki yapıcısı tanımlansın; birisini hiçbir parametre almayan, diğeri de bir adet int parametre alan. Örnek yapıcılar new operatörü kullanılarak çağırılırlar.


Liste<string> liste1 = new Liste<string>();
Liste<string> liste2 = new Liste<string>(10);

Diğer üyelerin aksine, örnek methodlar miras almazlar. Yani bir sınıfın kendi içinde tanıtılmış örnek yapıcısından başka bir örnek yapıcısı olamaz. Eğer bir sınıfın örnek yapıcısı yoksa, parametresiz boş bir örnek yapıcı otomatik olarak yaratılır.

Önemli: Yapıcılar tembel olmalıdırlar. En iyi pratik olarak yapıcılar içinde minimum iş yapılamıdır ki bu da basit bir şekilde argümanların daha sonra kullanımı için basitleştirmelidir. Örneğin; bir dosyanın ismini ya da o dosyanın yolunu veritabanı için tutmak istiyorsunuz ama dış kaynakları gerekli olmadığı sürece açmak istemiyorsunuz. Bu pratik, gerekli en az kaynağa erişimede emin olmak için yardımcı olur.

  • Özellikler (Properties)

C#’ta özellikler, alanların (fields) doğal uzantılarıdır. İkisi de ilişkilendirildikleri tiplerile birlikteki üyelerin isimleriyle, ilişkilendirildikleri alanların sözdizimi ile ve özelliklerle isimlendirilirler. Fakat, alanlardan farklı olarak, özellikler depolanmış bellek göstermezler. Bunun yerine, özelliklerin erişimcileri vardır ki onların değerlerini okuma ya da yazmada kullanılan ifadeleri belirtmede kullanılırlar.

Önemli: İyi bir uygulama tekniği olarak alanlara (özelliklerin verilerine) her zaman Getter ve Setter ile ulaşmak önerilir. Eğer Getter veya Setter’larınıza fonksiyonel işlevsellikler eklemek isterseniz, kodunuzu herhangi bir şekeilde kırmadan bunu kolaylıkla yapabilirsiniz.

Yukarıdaki anlatıma bir örnek verelim: Hiçbir zaman alanlarınızı (fields) public olarak tanımlamayın;


class İnek
{
public int Süt; // Kötü
}

Eğer belleğin yanında herhangi bir katman istemiyorsanız, bırakın derleyici sizin yerinize uygulasın;


class İnek
{
public int Süt {get; set;} // İyi
}

Bu sayede, istemci özelliğe Getter ve Setter’ı bağlar ve buna istinaden aşağıdaki şekilde derleyicinin uygulamasına etki edebilirsiniz;


class İnek
{
bool SüteSahip = false;
int Süt;
public int Süt {
get
{
if (!SüteSahip)
{
Süt = SütMakinesiUygula();
SüteSahip = true;
}
return Süt;
}
set
{
TersSütMakinesiUygula(value);
Süt = value;
}
}
}

Ayrıca, aşağıdaki kullanım ile hesaplanmış değer sizin programınızda kullanılır;


class İnek
{
public İnek()
{
Süt = SütMakinesiUygula();
}
public int Süt { get; private set; }
...
}

Bu durumda, özelliği tekrar hesaplıyoruz, ki neye ihtiyacımız olduğunu bilmediğimiz durumlarda bu gereksiz bir davranıştır.

Önemli: Özelliklere erişim tıpkı alanlara erişim gibi gözükür insanlara. Ve kişiler de doğal olarak alanlara erişim ile aynı performans göstereceklerini tahmin ederler. Eğer bir erişim belirgin bir iş ihtiyacı duyuyorsa (bir dosyadan okuma, veritabanında bir query çalıştırma gibi) bir method olarak açılmalıdır, bir özellik olarak değil. Bunu çağıranlar methodun daha fazla iş yaptığını düşünürler.

Aynı sebepten dolayı, özelliklere erişimde tekrarlı çağırımlar (koda müdahele etmeden) aynı değeri döndürmelidirler. DateTime.Now bu tavsiyeye uymayan örneklere sahiptir.

Bir özellik, bir alan ile aynı şekilde tanımlanır. İstisna olarak, noktalı virgülle bitirmek yerine Get ve Set erişimcilerini { ve } arasında kullanarak tanımsal biçimde alanlardan ayrılırlar. Hem Get hem de Set erişimcisine sahip özellikler okunur-yazılır (read-write) özellik, sadece Get erişimcisine sahip özellikler sadece okunur (read-only) ve sadece Set erişimcisine sahip özellikler de sadece yazılır (write-only) özellikler olarak tanımlanır.

Bir Get erişimcisi parametresiz bir method ve özellik tipini geri dönüş tipi olarak uygulayan bir method ile uyuşur. Belirlenen hedefin atamalar dışında, bir özellik bir ifadeyi referans ediyorsa, özelliğin Get erişimcisi, özelliğin değerini hesaplamada çağrılır.

Bir Set erişimcisi, value adında bir parametre içeren ve geri dönüş tipi olmayan bir method ile uyuşur. Bir özellik belirli bir atamayı ya da ++ ya da – gibi operandları referans ediyorsa, Set erişimcisi yeni değeri içeren bir argüman ile çağrılır.

Liste<T> sınıfı iki adet özellik tanımlar, Sayma ve Kapasite, sırasıyla sadece okunur ve okunur-yazılır özellikler. Aşağıdaki örnek bunların nasıl kullanıldığına bir örnektir:


Liste<string> isimler = new Liste<string>();
isimler.Kapasite = 100;   // Set erişimcisi çağrılır.
int i = isimler.Sayma;      // Get erişimcisi çağrılır.
int j = isimler.Kapasite;  // Get erişimcisi çağrılır.

Alanlarda ve özelliklerde benzer olarak, C# hem örnek özellikleri hem de static özellikleri destekler. Static özellikler static anahtar kelimesi ile tanımlanır, static anahtar sözcüğü kullanılmayan özellikler örnek özelliklerdir.

Bir özelliğin erişimcileri (Get ve Set) sanal (virtual) olabilirler. Eğer bir özellik tanımlaması virtual, abstract ya da override anahtar sözcüklerine sahipse, o özelliğin erişimcilerine uygular.

Önemli: Eğer bir sanal özellik, private erişimciye sahipse, o erişimci CLR içinde sanal olmayan methodtur ve türetilmiş sınıf içerisinde aşırı yüklenemez.

Özellikler aşağıdaki erişimcilere sahiptirler;

Statik değiştirici static
Erişim değiştirici public, internal, private, protected
Kalıtımsal değiştirici new, virtual, abstract, override, sealed
Yönetilemeyen kod değiştiricisi unsafe, extern
  • İndexleyiciler (İndexers)
    İndexleyiciler, diziler gibi, nesneleri indexleyebilen üyelerdir. Bir indexleyici, bir özellik ile aynı şekilde tanımlanır, tek fark olarak [ ] arasında parametre listesi hariç. Bu parametreler, indexleyicinin erişimcisi için uygundurlar. Özellikler gibi, indexleyiciler de read-write, read-only, write-only ya da bir indexleyici virtual tanımlanabilir.

Aşağıdaki List sınıfı string parametresi olan basit bir indexleyici tanımlamıştır;


List<string> isimler = new List<string>();
isimler.Add("Ülkü");
isimler.Add("Hülya");
isimler.Add("İlknur");
for (int i = 0; i < isimler.Count; i++) {
string s = isimler[i];
isimler[i] = s.ToUpper();
}

İndexleyiciler aşırı yüklenebilirler. Bunun anlamı sınıflar istedikleri uzunlukta parametre listeleri içeren indexleyici tanımlayabilirler.

  • Olaylar (Events)

Bir olay, sınıflara ya da nesnelere bildirim sağlanmasına olanak veren üyelerdir. Olaylar, tıpkı alanlar gibi tanımlanırlar. Fark olarak event anahtar kelimesi kullanılır ve tipi delegate tipi olmalıdır.

Jesse Liberty: Gerçekte, event anahtar sözcüğü sadece C#’ta delegate’in kullanılabilir olmasına bir yol göstericidir.

Chris Sells: event anahtar sözcüksüz, şunu yapabilirsiniz;


delegate void CalismaTamamlandi();
class Calisan
{
public  CalismaTamamlandi Tamamlandi; // Delegate alanı, bir event değil.

}
class  Patron
{
public void CalismaTamamlandi() { }
}
class Program
{
static void Main()
{
Patron Soner = new Patron();
Calisan isci = new Calisan();
isci.Tamamlandi += Soner.CalismaTamamlandi(); // Yapmak istediğiniz
isci.Tamamlandi = Soner.CalismaTamamlandi();  // Derleyicinin izin verdiği
}
}

Ne yazık ki, event sözcüğü ile birlikte, Tamamlandi() sadece delegate tipinde public bir alandır. Derleyici için de sorun oluşturmaz bu durum. Fakat event anahtar sözcüğünü eklediğimizde, += ve –= gibi operatörleri sınırlandırırsınız.


class Calisan
{
public event CalismaTamamlandi Tamamlandi;

}

isci.Tamamlandi += Soner.CalismaTamamlandi(); // Derleyici izin verir.
isci.Tamamlandi = Soner.CalismaTamamlandi(); // Derleyici hata verir.

Burada event sözcüğünün kullanımı sadece bir kere public bir alan oluşturmanın doğaln olmasıdır.Çünkü derleyici güvenli operatörlerin kullanımını daraltır.

Olay üyesi tanımlayan bir sınıf içerisinde, olaya delegate tipindeki bir alana erişimi gibi erişilebilir. Alan, olaylara eklenen olay tutucuları (event handler) temsil eden delegate’e bir referans saklar. Eğer herhangi bir event handler mevcut değilse, o alanın değeri null olur.

List<T> sınıfı basit bir olay üyesi olan Changed tanımlar ki bu yeni elemanların listeye eklenmesini gösterir. Changed olayı, OnChanged sanal methodundan kullanılır ki bu ilk önce olayın boş olup olmadığını kontrol eder. (Bunun anlamı hiçbir event handler’ın olmaması)

İstemciler olaylara olay tutucular (event handler) ile tepki gösterirler. Event handler’lar += operatörü ile eklenir ve –= operatörü ile silinebilirler. Aşağıdaki örnek, bir event handler’ın List<string>’in Changed olayına eklenmesini gösterir;


using System;
using System.Collections.Generic;

class Test
{
static int DegismeSayisi;
static void ListChanged(object sender, EventArgs e)
{
DegismeSayisi++;
}
static void Main()
{
List<string> isimler = new List<string>();
isimler.Changed += new EventHandler(ListChanged);
isimler.Add("Ülkü");
isimler.Add("Hülya");
isimler.Add("İlknur");
Console.WriteLine(DegismeSayisi); // Çıktı 3 olur.
}
}

Chris Sells: isimler.Changed += new EventHandler(ListChanged); yerine isimler.Changed += ListChanged; şeklinde bir kısaltma da kullanılabilir.

  • Yok Ediciler (Destructors)

Bir destructor, bir sınıf örneğini ya da nesnesini yoketmek için kullanılan uygulamaları içerir. Yokedicilerin parametreleri yoktur, erişim kelimelerine sahip değillerdir ve açık bir şekilde çağrılamazlar. Yokediciler otomatik olarak garbage collection zamanında çalıştırılırlar.

Garbage collector, nesnelerin ne zaman toplanacağına ve yokedicilerin ne zaman çalıştırılacağına imkan verir. Özellikle, yokedicilerin çağrılma zamanların rastgele değildir, ve yokediciler herhangi bir thread zamanında çalıştırılabilir. Bu nedenle, sınıflar yokedicileri  sadece başka çözümler olmadığı zamanda uygulamalıdırlar.

Önemli: Yokediciler bazen “Finalizer” olarak anılırlar. Bu isim aynı zamanda Garbage collector API’sinde de görünür; örneğin: GC.WaitForPendingFinalizers.

C#’ta Interface (Arayüz) Kavramı

C#’ta arayüzler, sınıf ve struct yapılarının ne yapması gerektiğini belirten yapılardır. Bir arayüz; method, özellik (properties), olay (event) ve indexleyici (indexers) yapılarını içerebilir. Bir arayüzün içindeki metodların gövdeleri yoktur. Yani bir arayüz hiçbir zaman hiç bir şekilde bir uygulama sağlamaz. Bir arayüzü birden fazla sınıf ya da struct kullanabildiği gibi, bir sınıf ya da struct yapısı da birden fazla arayüz uygulayabilirler.

Arayüzler çoklu kalıtımı (multiple inheritance) desteklerler. Örneğin; aşağıdaki ICombobox arayüzü hem ITextBox hem de IListBox arayüzlerinden kalıtım alır:

interface IControl 
{ 
    void Boyama(); 
} 
interface ITextBox : IControl 
{ 
    void YazıBelirler(string text); 
} 
interface IListBox : IControl 
{ 
    void OgeBelirle(string[] items); 
} 
interface IComboBox : ITextBox, IListBox { }

Sınıflar ve struct yapıları da çoklu kalıtımı desteklerler. Aşağıda, EditBox sınıfı hem IControl hem de IDataBound arayüzlerinden kalıtım alır:

interface IDataBound 
{ 
    void Bind(); 
} 
public class EditBox : IControl, IDataBound 
{ 
    public void Boyama() { } 
    public void Bind() {}

}

Bir sınıf ya da struct belirli bir arayüzü uyguladığında, o sınıf ya da struct örneği, dolaylı olarak arayüz tipine çevrilebilir. Örneğin;

EditBox editBox = new EditBox(); 
IControl control = editBox; 
IDataBound dataBound = editBox;

Arayüzler aşağıdaki nedenlerden dolayı özeldirler;

  • Bir sınıf birden fazla arayüz uygulayabilir. Buna karşılık, bir sınıf sadece bir sınıftan kalıtım alabilir.
  • Arayüz üyelerinin tümü dolaylı olarak soyuttur (abstract). Buna karşılık, bir sınıf hem soyut hem de somut üyelerin uygulanmasına olanak sağlar.
  • Struct yapıların arayüzleri uygulayabilir. Buna karşılık, bir struct yapısı bir sınıf yapısından kalıtım alamaz.

Bir arayüz tanımlanması tıpkı sınıf tanımlamasına benzer, fakat bir arayüz kendi üyelerinin hepsi dolaylı olarak soyut oldukları için bunlara herhangi bir uygulama sağlamaz. Bu üyeler, bu arayüzü uygulayan sınıf ya da struct yapıları tarafından kullanılırlar.

System.Collections namespace’inde bulunan IEnumerator arayüzünün tanımı şu şekildedir;

public interface IEnumerator 
    { 
        bool MoveNext(); 
        object Current { get; } 
        void Reset(); 
    }

Bir arayüz üyesi her zaman dolaylı olarak public özelliktedir ve herhangi bir erişim değiştiricisi tanımlanamaz. Bir arayüzü uygulamak onun tüm elemanlarını public olarak kullanılmasına olanak sağlamak anlamına gelir.

internal class GeriSayim : IEnumerable 
{ 
    int sayac = 10; 
    public bool MoveNext() { return sayac-- > 0; } 
    public object Current { get { return sayac; } } 
    public void Reset() { throw new NotSupportedException(); } 

}

Dolaylı olarak bir nesneyi herhangi bir arayüz tipine çevirebilirsiniz;

IEnumerator e = new Countdown();

Bir arayüz başka bir arayüzden türetilebilir. Bu sayede türetilen arayüz diğer arayüzün tüm üyelerini kalıtım alır. Örneğin;

public interface IGeriAl 
     { 
         void GeriAl(); 
     } 
     public interface IYenidenYap : IGeriAl 
     { 
         void YenidenYap(); 
     }

Burada IYenidenYap, IGeriAl arayüzünün tüm üyelerini kalıtım alır.

Açık Arayüz Uygulaması (Explicit Interface Implementation)

      Birden fazla arayüz uygulaması bazen üye imzalarının çarpışması sonuçları doğurabilir. Bu durumu arayüz üyesine uygulanan

açıkça uygulama(explicitly implementing)

    şeklinde çözebilirsiniz. Şu aşağıdaki koda göz atalım;
class Test 
{ 
    interface I1 
    { 
        void Foo(); 
    } 
    interface I2 
    { 
        int Foo(); 
    } 
    public class Arac : I1, I2 
    { 
        public void Foo() 
        { 
            Console.WriteLine("I1.Foo uygulamasi"); 
        }

        int I2.Foo() 
        { 
            Console.WriteLine("I2.Foo uygulaması"); 
            return 23; 
        } 

    } 
}

Görüldüğü üzere, hem I1 hem de I2 arayüzlerinin çelişkili Foo imzaları var. Arac sınıfı, açık bir şekilde I2 arayüzünün Foo methodunu uygular. Bu durum bu iki methodun aynı sınıf içerisinde bir arada olmasına olanak verir. Bu üyelerin çağrımını ancak arayüz çevrimleri ile sağlayabiliriz:

Arac a = new Arac(); 
a.Foo();       // I1.Foo uygulanır. 
(I1)a.Foo(); // I1.Foo uygulanır. 
(I2)a.Foo(); // I2.Foo uygulanır.

Arayüz Üyelerini Sanal Uygulama (Implementing Interface Members Virtually)

Açıkça uygulanmış arayüz üyeleri default olarak sealed tanımlıdır. Temel sınıfta bu üyeler virtual ya da abstract olarak tanımlanmak zorundadır. Örneğin;

using System; 
using System.Collections; 
class Test 
{ 
    public interface IGeriAl 
    { 
        void GeriAl(); 
    } 
    public class Sınıf1 : IGeriAl 
    { 
        public virtual void GeriAl() 
        { 
            Console.WriteLine("Sınıf1.GeriAl()"); 
        } 
    } 
    public class Sınıf2 : Sınıf1 
    { 
        public override void GeriAl() 
        { 
            Console.WriteLine("Sınıf2.GeriAl()"); 
        } 
    } 
    public static void Main() 
    { 
        Sınıf2 s = new Sınıf2(); 
        s.GeriAl();                  // Sınıf2.GeriAl() 
        ((IGeriAl)s).GeriAl();  // Sınıf2.GeriAl() 
        ((Sınıf1)s).GeriAl();    // Sınıf2.GeriAl() 
    } 
}

Bir arayüz elemanını temel sınıftan doğrudan çağırmak ya da alt sınıftan arayüz çağrısı ile çağırmak bize aynı sonucu verir.

Altsınıflarda Arayüzü Tekrar Uygulama

Bir altsınıf, temel sınıf tarafından uygulanmış arayüz üyelerini tekrar tekrar uygulayabilir. Tekrar uygulama, üye uygulamasını kaçırır ve temel sınıftaki üyeler virtual olsun ya da olmasın çalışır. Aşağıdaki örnek ile daha iyi anlatmaya çalışalım;

Bu örnekte; TextBox sınıfı IGeriAl.GeriAl() methodunu açıkça uygular. Bu yüzden virtual olarak işaretlenemezler. Override etmek için, RichTextBox sınıfı IGeriAl.GeriAl() methodunu yeniden uygulamak zorundadır.

public interface IGeriAl 
    { 
        void GeriAl(); 
    } 
    public class TextBox : IGeriAl 
    { 
        void IGeriAl.GeriAl() 
        { 
            Console.WriteLine("TextBox.GeriAl"); 
        } 
    } 
    public class RichTextBox : TextBox, IGeriAl 
    { 
        public new void GeriAl() 
        { 
            Console.WriteLine("RichTextBox.GeriAl"); 
        } 

    }

Altsınıfın uygulamasında arayüz çağrısı olarak tekrar uygulanmış üyeleri çağırmak için;

RichTextBox r = new RichTextBox(); 
r.GeriAl(); //RichTextBox.GeriAl 
((IGeriAl)r).GeriAl();  //RichTextBox.GeriAl

Peki ya TextBox sınıfımızı aşağıdaki şekilde değiştirirsek?

public class TextBox : IGeriAl 
    { 
        public void GeriAl() 
        { 
            Console.WriteLine("TextBox.GeriAl"); 
        }

    }

Bu özellik bize GeriAl() methodunu farklı bir şekilde çağırma olanağı sağlar.

RichTextBox r = new RichTextBox(); 
r.GeriAl();                   // RichTextBox.GeriAl 
((IGeriAl)r).GeriAl();    // RichTextBox.GeriAl 
((TextBox)r).GeriAl();  // TextBox.GeriAl

Bu üçüncü durum bize gösteriyor ki, bu durumda bir uygulama yapılması bir üyenin base sınıftan değil de direkt arayüz tarafından çağrılması durumlarında etkin hale gelir. Bu genellikle istenilmeyen bir durumdur.

Arayüzler ve Boxing

Bir struct yapısını bir arayüz yapısına çevirmek boxing işlemidir. Dolaylı olarak uygulanmış struct yapısını çağırmak boxing işlemi değildir.

         interface I 
        { 
            void Metod(); 
        }

        struct S : I 
        { 
            public void Metod() { } 
        }

        S str = new S(); 
        str.Metod(); //Boxing değil

        I i = S; 
        i.Metod(); //Arayüz yapısına çevirdiğimizde boxing gerçekleşir.

Sınıf vs. Arayüz Yazılması

Temel olarak;

  • Uygulama aşamasında doğal olarak paylaşıllan tipler için sınıf ya da alt sınıflar kullanın.
  • Bağımsız uygulamaya sahip tipler için arayüzler kullanın.

Aşağıdaki sınıflar göz önüne alalım;

    abstract class Hayvan { } 
    abstract class Kus : Hayvan { } 
    abstract class Bocek : Hayvan { } 
    abstract class Kusgiller : Hayvan { } 
    abstract class Etobur : Hayvan { }

    //Somut Sınıflar

    class DeveKusu : Kus { } 
    class Kartal : Kus, Kusgiller, Etobur { } //Geçersiz 
    class Arı : Bocek, Kusgiller { } //Geçersiz 
    class Pire : Bocek, Etobur { } //Geçersiz

Kartal, Arı ve Pire sınıfları derlenmezler çünkü birden fazla sınıftan kalıtım almaları yasaktır. Bunu çözmek için, bazı tipleri arayüz tipine çevirmek zorundayız. Tabi burada soru şudur, hangi tipleri? Genel kuralımızı takip ederek, Bocek ve Kus sınıfları uygulamalarını paylaşırlar. Bu yüzden bunlar sınıf olarak kalmak zorundadır. Buna karşılık, Kusgiller uçmak için bağımsız bir mekanizmaya sahiptir ve Etobur et yiyenler için bağımsız stratejilere sahiptir. Bu yüzden bu sınıfları arayüz tipine çevirmemiz gerekir.

interface IKusgiller { } 
interface IEtobur { }

C#’ta Virtual, Override ve Abstract Methodlar

C#’ta bir method örneği, virtual anahtar sözcüğü ile tanımlanırsa, o methoda virtual (sanal) method adı verilir.

Bir sanal method, ana sınıf tipinden ziyade, güncel atanmış tip ile ilişkilendirilmiş methodu kullanmanıza izin verir.

Bir sanal method türetilmiş sınıfı tarafından etkisiz hale getirilebilir. Bir method örneğinde override anahtar sözcüğü varsa, o method, kalıtım alınmış sanal methodu aynı method imzası ile geçersiz kılar. Bu tür bir sanal method eğer overload olmuşsa, tanımlanmış değişkenin data tipinden ziyade verinin gerçek sınıf tipi runtime zamanında kullanılır. Bunun anlamı; temel sınıf bir çok türetilmiş sınıf tarafından kullanılabilir.

Önemli: Buradaki ince bir nokta, override edilmiş bir sanal method, hala o sınıfın bir methodu olarak hesab katılır. Türetilmiş sınıf, sanal sınıf override edildiği zaman onu göstermek zorundadır. Bu override anahtar sözcüğünü yeni method oluşumunda kullanılarak gösterilir.

Bir abstract (soyut) method, uygulanmayan bir sanal methodtur. Bu methodlar, abstract anahtar sözcüğü ile tanımlanır ve sadece astract olarak tanımlanmış sınıflara izin verilir. Bir soyut sınıf, soyut olmayan türetilmiş tüm sınıflar tarafından override edilmelidir.

Aşağıdaki örnekte; soyut sınıf olarak tanımlanmış Deyim sınıfı, ki bir deyim ifade ağacı düğümü temsil eder, 3 tane de türetilmiş sınıf, Sabit, DegiskenReferansi ve İslem, ki bunlar da sabitlerin, değişken referanslarının ve aritmetik işlemlerin deyim ifade ağaçlarını uygularlar.


using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
public abstract class Deyim
{
public abstract double Hesapla(Hashtable vars);
}
public class Sabit : Deyim
{
double value;
public Sabit(double value)
{
this.value = value;
}
public override double Hesapla(Hashtable vars)
{
return value;
}
}
public class DegiskenReferansi : Deyim
{
string name;
public DegiskenReferansi(string name)
{
this.name = name;
}
public override double Hesapla(Hashtable vars)
{
object value = vars[name];
if (value == null)
{
throw new Exception("Bilinmeyen deşiken " + name);
}
return Convert.ToDouble(value);
}
}
public class İslem : Deyim
{
Deyim sol;
char op;
Deyim sag;

public İslem(Deyim sol, char op, Deyim sag)
{
this.sol = sol;
this.op = op;
this.sag = sag;
}

public override double Hesapla(Hashtable vars)
{
double x = sol.Hesapla(vars);
double y = sag.Hesapla(vars);
switch (op)
{
case '+': return x + y;
case '-': return x - y;
case '*': return x * y;
case '/': return x / y;
}
throw new Exception("Bilinmeyen operatör");
}
}
} 

Bu 4 sınıf, modern matematik işlemler için kullanılabilir. Örneğin, bu sınıf örneklerini kullanarak x + 7 deyimi şu şekilde gösterilebilir;


static void Main()
{
Deyim d = new İslem(new DegiskenReferansi("x"), '+', new Sabit(7));
} 

Burada Deyim sınıfının Hesapla methodu verilen değeri hesaplamak ve bir dobule değeri üretmek görevindedir. Bu method Hashtable adında değişken isimlerini ve değerlerini içeren bir parametre alır. Hesapla methodu, sanal soyut bir method olduğundan, bunun anlamı; soyut olmayan türetilmiş sınıflar güncel bir uygulama sağlamak için bu methodu override etmek zorundalar.

Sabit sınıfının Hesapla implementasyonu, basit olarak geriye depolanmış bir sabit (constant) döndürür. DegiskenReferansi implementasyonu, Hashtable içerisindeki değişken ismine bakar ve sonuçlanan değeri geri döndürür. İslem implementasyonun görevi, öncelikle sol ve sağ operandlarını (Recursif olarak çağırılan Hesapla() methodları ile) hesaplayarak, verilen aritmetik işlemi gerçekleştirmektir.

Aşağıdaki program Deyim sınıfını kullanarak x * (y + 3) işlemini farklı değerler kullanarak hesaplar;


static void Main()
{
Deyim d = new İslem(new DegiskenReferansi("x"), '*', new İslem(new DegiskenReferansi("y"), '+', new Sabit(3)));

Hashtable vars = new Hashtable();

vars["x"] = 2;
vars["y"] = 5;
Console.WriteLine(d.Hesapla(vars)); // Çıktı 16 olur.

vars["x"] = 3;
vars["y"] = 7;
Console.WriteLine(d.Hesapla(vars)); // Çıktı 30 olur.
}