Sanal Metotlar ve Devre Dışı Bırakma (Overriding) [11]

Sanal metot, temel sınıf içerisinde virtual olarak deklare edilen ve bir veya daha fazla türetilmiş sınıf içinde yeniden tanımlanan bir metotdur. Böylece, her türetilmiş sınıf bir sanal metodun kendine özgü bir versiyonuna sahip olabilir. Sanal metotlardan biri bir temel referansı tarafından çağrıldığında ortaya çıkan durum ilginçtir. Bu durumda, referans tarafından referansta bulunulan nesnenin tipine bağlı olarak c#, söz konusu metodun hangi versiyonunun çağrılacağını belirler ve bu belirleme çalışma zamanında gerçekleştirilir.
Yani, farklı nesnelere referansta bulunuluyorsa sanal metodun farklı versiyonları çalıştırılır. Bir başka deyişle, sanal metodun hangi versiyonunun çalıştırılacağını belirleyen referansta bulunan nesnenin tipidir. (referansın tipi değildir.) Dolayısıyla, eğer bir temel sınıf bir sanal metot içeriyorsa ve bu temel sınıftan başka sınıflar turetilmişse, bu durumda bir temel sınıf referansı aracılığıyla farklı nesne tiplerine referansta bulunulurken sanal metodun farklı versiyonları çalıştırılır.

Bir temel sınıf içinde bir metodu sanal olarak deklare etmek için metodun deklarasyonun önüne virtual anahtar kelimesini yerleştiririz. Bir sanal metot bir türetilmiş sınıf tarafından yeniden tanımlanırken override niteleyicisi kullanılır. Bu nedenle, bir sanal metodu bir türetilmiş sınıf içinde yeniden tanımlama işlemine metodu devre dışı bırakma(method overriding) denir. Bir metodu devre dışı bırakırken devre dışı bırakan metodun tip imzası, devre dışı bırakılmakla olan sanal metot ile aynı olmalıdır. Ayrıca, sanal metotlar
static veya abstract(bu bölüm içinde ele alınacaktır) olarak belirtilemezler.
Metotları devre dışı bırakma özelliği C#'ın en güçlü kavramlarından birinin temelini oluşturmaktadır: dinamik metot dağıtımı (dynamic method dispatch). Dinamik metot dağıtımı, devre dışı bırakılan bir fonksiyona yapılan çağrının derleme zamanında değil, çalışma zamanında çözüldüğü bir mekanizmadır. Dinamik metot dağıtımı önemlidir çünkü dinamik metot dağıtımı, C#'ın programın çalışması sırasında çok biçimliliği nasıl uyguladığının bir göstergesidir.
İşte bir örnek:

//Sanal bir metot gosterir.
using System;

class Base {
//Temel bir sinif icinde sanal metot olustur.
public virtual void who() {
Console.WriteLine("who() in Base");
}
}

class Derived1 : Base {
//who() metodunun turetilmis sinif icinden devre disi birak.
public override void who() {
Console.WriteLine("who() in Derived1");
}
}

class Derived2 : Base {
//who() metodunu bir baska turetilmis sinif icinden devre disi birak.
public override void who() {
Console.WriteLine("who() in Derived2");
}
}

class OverrideDemo {
public static void Main() {
Base base0b=new Base();
Derived1 d0b1=new Derived1();
Derived2 d0b2=new Derived2();
Base baseRef; // bir temel sinif referansi
baseRef=base0b;
baseRef.who();
baseRef=d0b1;
baseRef.who();
baseRef=d0b2;
baseRef.who();
}
}

Programın çıktısı aşağıdaki gibidir:

who() in Base
who() in Derived1
who() in Derived2

Bu program Base adında bir temel sınıf ve Derived1 ve Derived2 adında iki türetilmiş sınıf oluşturur. Base, who() adında bir metot deklare eder ve türetilmiş sınıflar bu metodu devre dışı bırakır. Main() metodu içinde Base, Derived1 ve Derived2 tipinde nesneler deklare edilir. Ayrıca baseRef adında Base tipinde bir referansta deklare edilmektedir. Program daha sonra hep tipten nesneye yapılan referansları baseRef'e atar ve who()'yu çağırmak için bu referansları kullanır. Çıktıdan görüldüğü gibi, who()'nun hangi versiyonunun
çalıştırılacağı çağrı sırasında kendisine referansta bulunulan nesnenin tipi ile belirlenir. Bu versiyon belirleme işleminde baseRef'in sınıf tipi baz alınmaz.
Sanal metodları devre dışı bırakmak bazen gerekli değildir. Eğer bir türetilmiş sınıf, mevcut bir sanal metodun kendisine ait bir versiyonunu sağlamıyorsa, bu durumda temel sınıf içindeki metot kullanılır. Örnek:

/*Bir sanal metot devre disi birakilmayinca
temel sinif icindeki metot kullanılır.*/
using System;

class Base {
//Temel sinif icinde sanal metot olustur.
public virtual void who() {
Console.WriteLine("who() in Base");
}
}

class Derived1 : Base {
//who()'yu turetilmis sinif icinden devre disi birak.
public override void Main() {
Console.WriteLine("who() in Derived1");
}
}

class Derived2 : Base {
// bu sinif who()'yu devre disi birakmaz.
}

class NoOverrideDemo {
public static void Main() {
Base base0b=new Base();
Derived1 d0b1=new Derived1();
Derived2 d0b2=new Derived2();
Base baseRef; // bir temel sinif referansi
baseRef=base0b;
baseRef.who();
baseRef=d0b1;
baseRef.who();
baseRef=d0b2;
baseRef.who();
}
}

Bu programdan elde edilen çıktı:

who() in Base
who() in Derived1
who() in Base

Bu örnekte Derived2, who()'yu devre dışı bırakmaz. Böylece who(), Derived2 nesnesi üzerinde çağrıldığı zaman Base içindeki who() çalıştırılır.
Çok katmanlı bir hiyerarşi söz konusu olduğunda, eğer bir türetilmiş sınıf bir sanal metodu devre dışı bırakmıyorsa, bu durumda hiyerarşinin yukarılarına doğru ilerlenir ve metodun ilk kez devre dışı bırakıldığı yerdeki metot çalıştırılır. Örneğin:

/*Cok katmanli bir hiyerarside,
hiyerarsinin yukarılarına dogru ilerlerken
devre disi birakilan ilk sanal metot
calistirilacak olan metottur.*/

using System;

class Base {
// Temel sinif icinde bir sanal metot olustur.
public virtual void who() {
Console.WriteLine("who() in Base");
}
}

class Derived1 : Base {
// who()'yu turetilmis sinif icinden devre disi birak.
public override void who() {
Console.WriteLine("who() in Derived1");
}
}

class Derived2 : Derived1 {
//Bu sinif da who()'yu devre disi birakmiyor.
}

class Derived3 : Derived2 {
//Bu sinif da who()'yu devre disi birakmiyor.
}

class NoOverrideDemo2 {
public static void Main() {
Derived3 d0b=new Derived3();
Base baseRef; // Bir temel sinif referansı
baseRef=d0b;
baseRef.who(); //Derived1'in icindeki who()'yu cagir
}
}

Çıktı:

who() in Derived1

Bu örnekte Base kalıtım yoluyla Derived1'e, Derived1 kalıtım yoluyla Derived2'ye, Derived2 ise kalıtım yoluyla Derived3'e aktarılır. Çıktıda da görüldüğü gibi who(), Derived3 veya Derived2 tarafından devre dış bırakılmadığı için who()'nun çalıştırılan versiyonu Derived1 içinde who()'yu devre dışı bırakan versiyondur, çünkü who()'nun rastlanan ilk versiyonu budur.

Kaynak: Herbert Schildt
Herkes İçin C#

Hiç yorum yok:

Yorum Gönder