Giriş: Farklı Nesneler, Ortak Arayüz, Özel Davranışlar

Nesne Yönelimli Programlamanın temel amacı, gerçek dünya problemlerini daha modüler, esnek ve yönetilebilir bir şekilde modellemektir. Kalıtım, sınıflar arasında “bir türüdür” (is-a) ilişkisi kurarak kod tekrarını azaltır ve ortak bir temel oluşturur (örn. Kedi bir Hayvan’dır, Daire bir Sekil’dir).

Ancak, sadece ortak özellikleri miras almak yeterli değildir. Genellikle alt sınıfların, üst sınıftan miras aldıkları bazı davranışları kendilerine özgü bir şekilde gerçekleştirmeleri gerekir. Örneğin, tüm Hayvan’ların SesCikar() metodu olabilir, ancak bir Kedi’nin ses çıkarması (“Miyav”) ile bir Kopek’in ses çıkarması (“Hav hav”) farklıdır. İşte Çok Biçimlilik (Polymorphism), bu tür durumları zarif bir şekilde ele almamızı sağlar.

Polimorfizm, bir üst sınıf veya arabirim türündeki bir referans değişkeninin, çalışma zamanında (runtime) farklı alt sınıf türlerindeki nesneleri işaret edebilmesi ve bu referans üzerinden bir metot çağrıldığında, nesnenin gerçek türüne uygun olan metodun (alt sınıfta geçersiz kılınmış olan metodun) çalıştırılması yeteneğidir.

Yani, elimizde bir Hayvan referansı olsa bile, eğer o referans o anda bir Kedi nesnesini tutuyorsa, hayvanRef.SesCikar() çağrısı Kedi’nin “Miyav” metodunu çalıştırır. Eğer aynı referans daha sonra bir Kopek nesnesini tutarsa, aynı hayvanRef.SesCikar() çağrısı bu kez Kopek’in “Hav hav” metodunu çalıştırır. Kodun çağrı yapan kısmı (hayvanRef.SesCikar()) değişmezken, sergilenen davranış (çıkan ses) referansın işaret ettiği nesnenin gerçek türüne göre değişir. İşte bu “birçok şekle girebilme” yeteneği polimorfizmdir.

C#’ta bu çalışma zamanı polimorfizmini sağlamanın ana yolu Metot Geçersiz Kılma (Method Overriding)’dır.

Bölüm 1: Metot Geçersiz Kılma (Method Overriding) Mekanizması

Metot geçersiz kılma, bir alt sınıfın, üst sınıftan miras aldığı bir metodu (belirli koşullar altında) kendi özel uygulamasıyla yeniden tanımlamasıdır.

Anahtar Kelimeler:

virtual: Üst sınıftaki bir metodun (veya özelliğin), alt sınıflar tarafından geçersiz kılınabileceğini (override edilebileceğini) belirtmek için kullanılır. virtual metotların mutlaka bir gövdesi (varsayılan uygulaması) olmalıdır.
override: Alt sınıftaki bir metodun, üst sınıftaki virtual veya abstract (ya da başka bir override) bir metodu geçersiz kıldığını belirtmek için kullanılır. Bu anahtar kelime zorunludur ve derleyiciye niyetinizi açıkça belirtir.
abstract: (Soyut Sınıflar konusunda detaylı işlendi) Üst sınıftaki bir metodun gövdesi olmadığını ve alt (somut) sınıflar tarafından override edilmesinin zorunlu olduğunu belirtir. Soyut metotlar örtük olarak sanaldır (virtual yazılmaz ama override edilebilirler).
Kurallar:

İmza Eşleşmesi: override edilen metodun imzası (adı, parametre sayısı, türleri ve sırası) üst sınıftaki virtual veya abstract metotla tamamen aynı olmalıdır. (Dönüş türü C# 9.0 itibarıyla kovaryant olabilir, yani alt sınıfın dönüş türü üst sınıfın dönüş türünden türemiş bir tür olabilir, ancak bu daha ileri bir konudur).
Erişim Belirleyicisi: override eden metodun erişim belirleyicisi, geçersiz kıldığı üst sınıf metodunun erişim belirleyicisiyle aynı veya daha geniş olmalıdır (örn. protected virtual bir metot public override edilebilir, ama public virtual bir metot protected override edilemez). private metotlar virtual olamaz ve dolayısıyla override edilemez.
static Metotlar: Statik metotlar virtual, abstract veya override olamazlar. Kalıtım ve geçersiz kılma, nesne örnekleri üzerinden çalışır.
Çalışma Zamanı Davranışı (Late Binding / Dynamic Dispatch):

Polimorfizmin sihri burada yatar. Bir üst sınıf referansı (Hayvan hayvanRef) bir alt sınıf nesnesini (new Kedi()) tutarken, virtual veya abstract olarak tanımlanmış ve alt sınıfta override edilmiş bir metot (hayvanRef.SesCikar()) çağrıldığında:

Derleyici, çağrının geçerli olduğunu referansın türüne (Hayvan) göre kontrol eder (yani Hayvan sınıfında SesCikar diye bir metot var mı?).
Çalışma zamanında (runtime), CLR referansın o an gerçekte hangi nesneyi işaret ettiğine (Kedi nesnesi) bakar.
Eğer o nesnenin sınıfında (Kedi sınıfında) metot override edilmişse, alt sınıfın override edilmiş versiyonu çalıştırılır.
Eğer alt sınıf metodu override etmemişse (sadece virtual durumunda mümkün), kalıtım zincirinde yukarı doğru gidilir ve bulunan ilk uygulama (genellikle üst sınıftaki virtual metot) çalıştırılır.
Bu işleme geç bağlama (late binding) veya dinamik gönderim (dynamic dispatch) denir. Hangi metodun çalıştırılacağına derleme zamanında değil, çalışma zamanında karar verilir.

Örnek:

using System;
using System.Collections.Generic;
public class Cizilebilir
{
public virtual void Ciz() // virtual: Geçersiz kılınabilir
{
Console.WriteLine("Temel bir şekil çiziliyor.");
}
}
public class Daire : Cizilebilir
{
public double Yaricap { get; set; }
public override void Ciz() // override: Üstteki Ciz'i geçersiz kılıyor
{
Console.WriteLine($"Bir daire çiziliyor (Yarıçap: {Yaricap}).");
}
}
public class Kare : Cizilebilir
{
public double Kenar { get; set; }
public override void Ciz() // override: Üstteki Ciz'i geçersiz kılıyor
{
Console.WriteLine($"Bir kare çiziliyor (Kenar: {Kenar}).");
}
}
public class Ucgen : Cizilebilir
{
// Ciz metodunu override ETMEZSEK, Cizilebilir'deki varsayılan çalışır.
// public override void Ciz()
// {
// Console.WriteLine("Bir üçgen çiziliyor.");
// }
}
public class CizimProgrami
{
public static void Main(string[] args)
{
// Farklı şekil nesneleri oluşturma
Cizilebilir sekil1 = new Daire { Yaricap = 5 };
Cizilebilir sekil2 = new Kare { Kenar = 4 };
Cizilebilir sekil3 = new Ucgen();
Cizilebilir sekil4 = new Cizilebilir(); // Temel sınıf örneği
// Tüm şekilleri bir listede tutma (Polimorfizm sayesinde mümkün)
List sekiller = new List { sekil1, sekil2, sekil3, sekil4 };
Console.WriteLine("Şekiller çiziliyor:");
foreach (Cizilebilir sekil in sekiller)
{
// Aynı metot çağrısı (sekil.Ciz()), farklı davranışlar sergiliyor!
sekil.Ciz();
}
}
}
Çıktı:

Şekiller çiziliyor:
Bir daire çiziliyor (Yarıçap: 5).
Bir kare çiziliyor (Kenar: 4).
Temel bir şekil çiziliyor. // Ucgen override etmediği için temel sınıfınki çalıştı
Temel bir şekil çiziliyor. // Cizilebilir örneği için temel sınıfınki çalıştı
Bu örnekte, sekiller listesindeki her bir sekil değişkeni Cizilebilir türündedir, ancak çalışma zamanında farklı nesneleri (Daire, Kare, Ucgen, Cizilebilir) işaret ederler. sekil.Ciz() çağrıldığında, CLR nesnenin gerçek türüne bakar ve o türe uygun olan Ciz metodunu (override edilmişse onu, edilmemişse üst sınıftakini) çalıştırır.

Bölüm 2: base Anahtar Kelimesi ile Üst Sınıf Metodunu Çağırma

Bir alt sınıf, bir metodu override ettiğinde, bazen üst sınıfın orijinal davranışını tamamen yok saymak yerine, onu genişletmek veya öncesinde/sonrasında ek işlemler yapmak isteyebilir. Bu durumda, override edilen metot içinden üst sınıfın metodunu çağırmak için base anahtar kelimesi kullanılır.

Sözdizimi:

public override DonusTuru MetotAdi(parametreler)
{
// Override eden metot içinden üst sınıfın aynı metodunu çağırma
base.MetotAdi(argümanlar);
// Alt sınıfa özgü ek işlemler...
}
Örnek:

public class GuvenlikKamerasi : ElektronikCihaz // Varsayımsal
{
public override void Ac() // Üst sınıftaki virtual Ac'ı override ediyor
{
Console.WriteLine("Güvenlik kamerası başlatılıyor...");
base.Ac(); // Üst sınıfın (ElektronikCihaz) Ac metodunu çağır
KayitBaslat(); // Alt sınıfa özgü ek işlem
}
private void KayitBaslat()
{
Console.WriteLine("Kayıt başlatıldı.");
}
// ElektronikCihaz'da tanımlı varsayalım:
// public virtual void Ac() { Console.WriteLine("Elektronik cihaz açıldı."); }
}
base kullanımı, üst sınıfın sağladığı temel işlevselliği korurken alt sınıfın kendi özelleştirmelerini eklemesine olanak tanır.

Bölüm 3: abstract Metotlar ve Polimorfizm

Soyut metotlar (abstract), polimorfizmin zorunlu bir şeklidir.

Soyut bir metot, üst (abstract) sınıfta sadece imzasıyla tanımlanır, gövdesi yoktur.
Miras alan somut alt sınıflar, bu metodu override etmek ve bir gövde sağlamak zorundadır.
Polimorfik davranış virtual/override ile aynıdır: Üst sınıf referansı üzerinden çağrıldığında, nesnenin gerçek türündeki override edilmiş uygulama çalışır.
abstract metotlar, “Bu davranışı sağlamak zorundasın, ama nasıl yapacağın sana kalmış” mesajını verirken, polimorfizm bu farklı “nasıl”ların ortak bir arayüz üzerinden kullanılabilmesini sağlar. Yukarıdaki Sekil örneğindeki Alan özelliği ve Ciz() metodu abstract olarak tanımlanıp alt sınıflarda override edilerek polimorfizm sağlanmıştır.

Bölüm 4: Metot Geçersiz Kılma (override) vs. Metot Gizleme (new)

Daha önce Kalıtım konusunda değinildiği gibi, override ve new anahtar kelimeleri farklı davranışlara yol açar:

override: Üst sınıftaki virtual veya abstract metodu geçersiz kılar. Polimorfizmi destekler. Üst sınıf referansı kullanıldığında bile alt sınıfın metodu çalışır. Bu genellikle istenen davranıştır.
new: Üst sınıftaki metotla aynı isimde yeni, bağımsız bir metot tanımlar (üsttekini gizler). Polimorfizmi desteklemez. Üst sınıf referansı kullanıldığında üst sınıfın metodu, alt sınıf referansı kullanıldığında alt sınıfın metodu çalışır. Genellikle kafa karıştırıcıdır ve kaçınılmalıdır.
. Kural: Polimorfik davranış istediğinizde (ki genellikle istersiniz), üst sınıfta metodu virtual (veya abstract) yapın ve alt sınıfta override kullanın.

Bölüm 5: Polimorfizmin Avantajları

Metot geçersiz kılma yoluyla elde edilen polimorfizm, OOP’nin en önemli faydalarından bazılarını sunar:

Esneklik ve Genişletilebilirlik: Sisteme yeni alt sınıflar eklemek kolaylaşır. Yeni bir Sekil türü (Besgen gibi) eklediğimizde, mevcut CizimProgrami kodunu ( foreach döngüsünü) değiştirmemize gerek kalmaz. Yeni Besgen sınıfı Ciz() metodunu override ettiği sürece, sekil.Ciz() çağrısı otomatik olarak doğru davranışı sergileyecektir. Bu, Açık/Kapalı Prensibine (Open/Closed Principle) uymayı kolaylaştırır (kod genişlemeye açık, değişikliğe kapalı olmalıdır).
Gevşek Bağlılık (Loose Coupling): Kodun üst sınıfa veya arabirime bağımlı olması, alt sınıfların somut uygulamalarına olan bağımlılığı azaltır. CizimProgrami’nın Daire veya Kare sınıflarını doğrudan bilmesine gerek yoktur, sadece Cizilebilir arayüzünü bilmesi yeterlidir. Bu, sistemin parçalarını değiştirmeyi veya test etmeyi kolaylaştırır.
Kodun Basitleştirilmesi: Farklı türdeki nesneler için ayrı ayrı if/else if veya switch blokları yazmak yerine (tür kontrolü yaparak), polimorfizm sayesinde tek bir metot çağrısı (sekil.Ciz()) ile her nesnenin kendi doğru davranışını sergilemesi sağlanır. Bu, kodu daha kısa, daha temiz ve daha okunabilir hale getirir.
Kod Tekrarının Azaltılması: Ortak davranışlar üst sınıfta toplanırken, özelleştirilmiş davranışlar override ile alt sınıflarda tanımlanır.
Bölüm 6: Arabirimler (Interfaces) ve Polimorfizm

Polimorfizm sadece sınıf kalıtımıyla sınırlı değildir; arabirimler aracılığıyla da güçlü bir şekilde uygulanır.

Bir arabirim, belirli metot imzalarını içeren bir sözleşmedir.
Bir sınıf bir arabirimi uyguladığında, o arabirimdeki tüm metotları sağlamak zorundadır.
Bir arabirim türündeki referans değişkeni, o arabirimi uygulayan herhangi bir sınıfın nesnesini tutabilir.
Bu arabirim referansı üzerinden bir metot çağrıldığında, çalışma zamanında nesnenin gerçek sınıfındaki uygulama çalıştırılır.
Örnek:

public interface IHareketEdebilir
{
void HareketEt();
}
public class Robot : IHareketEdebilir
{
public void HareketEt()
{
Console.WriteLine("Robot tekerlekleri üzerinde hareket ediyor.");
}
}
public class Insan : IHareketEdebilir
{
public void HareketEt()
{
Console.WriteLine("İnsan yürüyor.");
}
}
public class Kurbaga : IHareketEdebilir
{
public void HareketEt()
{
Console.WriteLine("Kurbağa zıplıyor.");
}
}
// Kullanım
List hareketliler = new List();
hareketliler.Add(new Robot());
hareketliler.Add(new Insan());
hareketliler.Add(new Kurbaga());
Console.WriteLine("\nHareketler (Interface Polymorphism):");
foreach (IHareketEdebilir hareketli in hareketliler)
{
// Aynı metot çağrısı, farklı davranışlar!
hareketli.HareketEt();
}
Çıktı:

Hareketler (Interface Polymorphism):
Robot tekerlekleri üzerinde hareket ediyor.
İnsan yürüyor.
Kurbağa zıplıyor.
Arabirimler, farklı sınıf hiyerarşilerinden gelen nesnelerin ortak bir “yetenek” üzerinden polimorfik olarak ele alınmasını sağlar, bu da C#’ın tekli sınıf kalıtımı kısıtlamasını aşmak için güçlü bir yoldur.

Bölüm 7: Pratik Uygulamalar

Polimorfizm ve metot geçersiz kılma, sayısız pratik senaryoda kullanılır:

UI Kütüphaneleri: Farklı kontrol türleri (Button, TextBox, Checkbox) ortak bir Control temel sınıfından türeyebilir ve Draw() veya HandleClick() gibi metotları kendi özel görünümleri veya davranışları için geçersiz kılabilirler.
Oyun Geliştirme: Farklı düşman türleri (Goblin, Orc, Dragon) ortak bir Dusman sınıfından türeyip Saldir() veya HareketEt() metotlarını farklı şekillerde uygulayabilirler.
Veri İşleme/Serileştirme: Farklı veri formatlarını (JSON, XML, CSV) işleyen sınıflar, ortak bir IDataSerializer arabirimini uygulayıp Serialize() ve Deserialize() metotlarını kendi formatlarına göre gerçekleştirebilirler.
Ödeme Sistemleri: Farklı ödeme yöntemleri (KrediKartiOdeme, HavaleOdeme, PayPalOdeme) ortak bir IOdemeYontemi arabirimini uygulayarak OdemeyiYap() metodunu kendi özel adımlarıyla gerçekleştirebilirler.
Loglama Mekanizmaları: Farklı hedeflere (Dosya, Veritabanı, Konsol, Bulut Servisi) log yazan sınıflar, ortak bir ILogger arabirimini uygulayıp Log() metodunu kendi hedeflerine göre yazabilirler.
Bölüm 8: Sonuç — Esnekliğin ve Genişletilebilirliğin Anahtarı

Çok Biçimlilik (Polymorphism), C#’ta Nesne Yönelimli Programlamanın en güçlü ve temel kavramlarından biridir. Metot Geçersiz Kılma (virtual, override, abstract) mekanizması aracılığıyla, bir üst sınıf veya arabirim referansı üzerinden farklı alt türlerdeki nesnelerle tutarlı bir şekilde çalışırken, her nesnenin kendi özel davranışını sergilemesini sağlar.

Bu yetenek, kodumuzu daha esnek hale getirir, çünkü yeni türler eklemek mevcut kodu minimum düzeyde etkiler. Kodun genişletilebilirliğini artırır ve gevşek bağlılığı teşvik eder, bu da sistemlerin daha kolay test edilmesini ve bakımının yapılmasını sağlar. Polimorfizm, karmaşık if/else veya switch yapılarına olan ihtiyacı azaltarak kodu basitleştirir ve okunabilirliğini artırır.

İster sınıf kalıtımı (virtual/override) ister arabirim uygulaması yoluyla olsun, polimorfizmi anlamak ve etkili bir şekilde kullanmak, C#’ta modüler, sürdürülebilir ve güçlü uygulamalar geliştirmenin anahtarıdır. Bu, nesnelerin sadece veri taşımakla kalmayıp, aynı zamanda bağlama göre farklı “şekillerde” davranabilmelerini sağlayan temel bir OOP prensibidir.

Abdulkadir Güngör - Kişisel WebSite
Abdulkadir Güngör - Kişisel WebSite
Abdulkadir Güngör - Özgeçmiş
Github
Github
Linkedin