Giriş: Bilgi ve Davranışın Aktarımı — Neden Kalıtım?
Gerçek dünyayı düşündüğümüzde, nesneler arasında doğal hiyerarşiler ve ilişkiler görürüz. Örneğin, bir “Kedi” bir tür “Hayvan”dır. Bir “Otomobil” bir tür “Taşıt”tır. Bu “bir türüdür” (is-a) ilişkisi, ortak özellikleri ve davranışları paylaşmayı ima eder. Tüm hayvanların belirli bir yaşam döngüsü, beslenme şekli vardır; tüm taşıtların hareket etme yeteneği, bir kapasitesi bulunur.
OOP’de kalıtım, bu gerçek dünya ilişkisini kodda modellememizi sağlar. Bir sınıfın ( alt sınıf, türetilmiş sınıf veya child class olarak adlandırılır), başka bir sınıfın ( üst sınıf, temel sınıf veya parent class olarak adlandırılır) public ve protected üyelerini (alanlar, özellikler, metotlar) otomatik olarak devralmasına olanak tanır.
Kalıtımın Temel Faydaları:
Kodun Yeniden Kullanılabilirliği (Code Reusability): Üst sınıfta tanımlanan ortak özellikler ve metotlar, alt sınıflarda tekrar tekrar yazılmaz. Alt sınıflar bu üyeleri doğrudan miras alır ve kullanabilir. Bu, kod miktarını azaltır ve tutarlılığı artırır.
Mantıksal Hiyerarşi ve Organizasyon: Sınıflar arasında doğal bir “bir türüdür” ilişkisi kurarak kodun yapısını daha anlaşılır ve organize hale getirir. Kavramsal olarak ilişkili sınıfları bir çatı altında toplar.
Genişletilebilirlik (Extensibility): Mevcut bir sınıfın işlevselliğini, onu doğrudan değiştirmeden, ondan yeni bir sınıf türeterek genişletebilir veya özelleştirebiliriz. Bu, mevcut kodun korunmasına yardımcı olur.
Çok Biçimlilik (Polymorphism) İçin Temel: Kalıtım, çok biçimliliğin (özellikle metot geçersiz kılma yoluyla) uygulanabilmesi için temel bir mekanizmadır. Üst sınıf türündeki bir referansın, alt sınıf nesnelerini tutabilmesini ve çalışma zamanında nesnenin gerçek türüne uygun metodun çağrılmasını sağlar.
Bakım Kolaylığı: Ortak bir işlevsellikte değişiklik yapılması gerektiğinde, bu değişikliği sadece üst sınıfta yapmak genellikle yeterli olur ve değişiklik otomatik olarak tüm alt sınıflara yansır.
Bölüm 1: C#’ta Kalıtım Söz Dizimi (:)
C#’ta bir sınıfın başka bir sınıftan miras aldığını belirtmek için sınıf tanımında alt sınıfın adından sonra iki nokta üst üste (:) ve ardından üst sınıfın adı yazılır.
Sözdizimi:
class UstSinif
{
// Üst sınıf üyeleri
}
class AltSinif : UstSinif // AltSinif, UstSinif'tan miras alır
{
// Alt sınıfa özgü ek üyeler veya
// Üst sınıftan miras alınan üyeleri geçersiz kılma (override)
}
Önemli Kurallar:
Tekli Sınıf Kalıtımı (Single Class Inheritance): C#’ta bir sınıf, doğrudan yalnızca bir tane üst sınıftan miras alabilir. Bu, Java’da da böyledir ve C++’taki çoklu kalıtımın getirdiği “ölümcül elmas problemi” gibi karmaşıklıkları önlemek için yapılmış bir tasarım tercihidir.
// class A { } // class B { } // class C : A, B { } // HATA! C# çoklu sınıf kalıtımını desteklemez.
Çoklu Arabirim Uygulaması: Bir sınıf tek bir üst sınıftan miras alırken, aynı anda birden fazla arabirimi (interface) uygulayabilir. Arabirimler :’dan sonra üst sınıftan sonra virgülle ayrılarak listelenir.
interface IArayuz1 { } interface IArayuz2 { } class D : UstSinif, IArayuz1, IArayuz2 { /* ... */ }
System.Object Temel Sınıfı: C#’ta açıkça başka bir sınıftan miras almayan tüm sınıflar, örtük olarak System.Object temel sınıfından miras alır. System.Object, tüm .NET türleri için nihai temel sınıftır ve ToString(), Equals(), GetHashCode(), GetType() gibi temel metotları sağlar.
Örnek Temel Kalıtım:
using System;
// Temel (Üst) Sınıf
public class Hayvan
{
public string Ad { get; set; }
public int Yas { get; set; }
// Üst sınıf yapıcısı
public Hayvan(string ad, int yas)
{
Console.WriteLine("Hayvan yapıcısı çalıştı.");
this.Ad = ad;
this.Yas = yas;
}
// Tüm hayvanların sahip olduğu ortak metot
public void Beslen()
{
Console.WriteLine($"{Ad} besleniyor...");
}
// Alt sınıfların geçersiz kılabileceği sanal metot
public virtual void SesCikar()
{
Console.WriteLine("Hayvan sesi...");
}
}
// Türetilmiş (Alt) Sınıf
public class Kopek : Hayvan // Hayvan sınıfından miras alıyor
{
public string Cins { get; set; }
// Alt sınıf yapıcısı (base() ile üst sınıf yapıcısını çağırmalı)
public Kopek(string ad, int yas, string cins) : base(ad, yas) // Üst sınıf yapıcısını çağır
{
Console.WriteLine("Kopek yapıcısı çalıştı.");
this.Cins = cins; // Alt sınıfa özgü özelliği ayarla
}
// Alt sınıfa özgü metot
public void Havla()
{
Console.WriteLine($"{Ad} havlıyor: Hav hav!");
}
// Üst sınıftaki virtual metodu geçersiz kılma (override)
public override void SesCikar()
{
// base.SesCikar(); // İstersek üst sınıfın metodunu da çağırabiliriz
Havla(); // Kendi havlama davranışını uygula
}
}
// Başka bir türetilmiş sınıf
public class Kedi : Hayvan
{
public bool TuyTopuVar { get; set; }
public Kedi(string ad, int yas) : base(ad, yas)
{
Console.WriteLine("Kedi yapıcısı çalıştı.");
TuyTopuVar = true; // Varsayılan
}
// Alt sınıfa özgü metot
public void Miyavla()
{
Console.WriteLine($"{Ad} miyavlıyor: Miyav!");
}
// Üst sınıftaki virtual metodu geçersiz kılma
public override void SesCikar()
{
Miyavla();
}
}
// Kullanım
public class KalitimTest
{
public static void Main(string[] args)
{
Kopek kopek1 = new Kopek("Karabaş", 3, "Kangal");
Kedi kedi1 = new Kedi("Pamuk", 2);
// Miras alınan özelliklere erişim
Console.WriteLine($"{kopek1.Ad}, {kopek1.Yas} yaşında bir {kopek1.Cins}.");
Console.WriteLine($"{kedi1.Ad}, {kedi1.Yas} yaşında.");
// Miras alınan metodu çağırma
kopek1.Beslen(); // Karabaş besleniyor...
kedi1.Beslen(); // Pamuk besleniyor...
// Alt sınıfa özgü metotları çağırma
kopek1.Havla(); // Karabaş havlıyor: Hav hav!
kedi1.Miyavla();// Pamuk miyavlıyor: Miyav!
// Geçersiz kılınmış metodu çağırma (Polimorfizm)
kopek1.SesCikar(); // Karabaş havlıyor: Hav hav! (Kopek'in override ettiği çalışır)
kedi1.SesCikar(); // Pamuk miyavlıyor: Miyav! (Kedi'nin override ettiği çalışır)
// Üst sınıf referansı ile alt sınıf nesnesine erişme (Polimorfizm)
Console.WriteLine("\nPolimorfizm ile Ses Çıkarma:");
Hayvan hayvanRef1 = kopek1; // Geçerli (Kopek bir Hayvan'dır)
Hayvan hayvanRef2 = kedi1; // Geçerli (Kedi bir Hayvan'dır)
hayvanRef1.SesCikar(); // Çalışma zamanında kopek1'in override ettiği metot çağrılır!
hayvanRef2.SesCikar(); // Çalışma zamanında kedi1'in override ettiği metot çağrılır!
// hayvanRef1.Havla(); // Hata! Hayvan türünde Havla metodu yok.
// Metot çağrısı referansın türüne göre derlenir,
// ancak override edilmişse çalışma zamanında nesnenin gerçek türüne göre çalıştırılır.
}
}
Bölüm 2: Üye Erişimi ve Erişim Belirleyiciler
Bir alt sınıfın, üst sınıftan miras aldığı üyelere nasıl erişebileceği, o üyelerin erişim belirleyicilerine (access modifiers) bağlıdır.
public: Üst sınıftaki public üyeler, alt sınıflar tarafından (veya herhangi bir yerden) doğrudan erişilebilir ve kullanılabilir.
protected: Üst sınıftaki protected üyeler, sadece üst sınıfın kendi içinden ve ondan türeyen alt sınıfların içinden erişilebilir. Sınıf dışından veya ilgisiz sınıflardan erişilemez. Alt sınıflara belirli bir iç işlevsellik veya veri sağlamak ama bunu tamamen public yapmamak için kullanılır.
private: Üst sınıftaki private üyeler, sadece tanımlandıkları üst sınıfın içinden erişilebilir. Alt sınıflar tarafından doğrudan erişilemezler. Bu, üst sınıfın iç uygulama detaylarını tamamen gizlemesini sağlar (en güçlü kapsülleme).
internal: Üst sınıftaki internal üyeler, aynı derleme (assembly) içindeki alt sınıflar tarafından erişilebilir.
protected internal: Aynı derleme içinden VEYA farklı derlemedeki alt sınıflardan erişilebilir.
private protected: Sadece aynı derleme içindeki alt sınıflardan erişilebilir.
Örnek (Erişim):
public class Temel
{
public string GenelBilgi = "Public Alan";
protected int KorunanVeri = 10;
private string GizliAnahtar = "ABC";
internal string DerlemeIci = "Internal";
public void GenelMetot() { /.../ }
protected void KorunanMetot() { /.../ }
private void GizliMetot() { /.../ }
}
public class Turetilmis : Temel
{
public void TestErisim()
{
Console.WriteLine(GenelBilgi); // Erişilebilir (Public)
Console.WriteLine(KorunanVeri); // Erişilebilir (Protected)
// Console.WriteLine(GizliAnahtar); // HATA! Erişilemez (Private)
Console.WriteLine(DerlemeIci); // Erişilebilir (Internal - aynı derlemede ise)
GenelMetot(); // Erişilebilir (Public)
KorunanMetot(); // Erişilebilir (Protected)
// GizliMetot(); // HATA! Erişilemez (Private)
}
}
public class IlgisizSinif
{
public void TestErisimDisaridan()
{
Temel t = new Temel();
Console.WriteLine(t.GenelBilgi); // Erişilebilir (Public)
// Console.WriteLine(t.KorunanVeri); // HATA! Erişilemez (Protected)
// Console.WriteLine(t.GizliAnahtar); // HATA! Erişilemez (Private)
Console.WriteLine(t.DerlemeIci); // Erişilebilir (Internal - aynı derlemede ise)
t.GenelMetot(); // Erişilebilir (Public)
// t.KorunanMetot(); // HATA! Erişilemez (Protected)
// t.GizliMetot(); // HATA! Erişilemez (Private)
}
}
Bölüm 3: Yapıcılar (Constructors) ve Kalıtım (base)
Alt sınıflar üst sınıfların yapıcılarını miras almazlar. Her sınıf kendi yapıcılarından sorumludur. Ancak, bir alt sınıf nesnesi oluşturulduğunda, üst sınıfın da başlatılması gerekir. Bu nedenle, alt sınıf yapıcısının mutlaka üst sınıfın bir yapıcısını çağırması gerekir.
Örtük Çağrı: Eğer alt sınıf yapıcısı açıkça bir üst sınıf yapıcısı çağırmazsa (base(…) kullanmazsa), derleyici otomatik olarak üst sınıfın parametresiz yapıcısını (base()) çağırmaya çalışır.
Açık Çağrı (base(…)): Alt sınıf yapıcısı, üst sınıfın belirli bir (genellikle parametreli) yapıcısını çağırmak için base anahtar kelimesini kullanır. base(…) çağrısı, alt sınıf yapıcısının imzasından sonra, gövdesinden önce : ile belirtilir. Üst sınıf yapıcısına gönderilecek argümanlar base() parantezleri içine yazılır.
Zorunluluk: Eğer üst sınıfın public veya protected bir parametresiz yapıcısı yoksa, alt sınıfın tüm yapıcıları açıkça base(…) kullanarak üst sınıfın mevcut yapıcılarından birini çağırmak zorundadır. Aksi takdirde derleme hatası alınır.
Örnek (Yukarıdaki Hayvan/Kopek örneğinden):
public class Hayvan
{
public string Ad { get; set; }
// Parametresiz yapıcı YOK! Sadece parametreli var.
public Hayvan(string ad) { this.Ad = ad; Console.WriteLine("Hayvan(ad) yapıcısı"); }
}
public class Kopek : Hayvan
{
public string Cins { get; set; }
// Hatalı Yapıcı (Üst sınıfın parametresiz yapıcısı yok!)
// public Kopek(string cins) { this.Cins = cins; } // HATA! base() çağrılamaz.
// Doğru Yapıcı (base(ad) ile üst sınıf yapıcısını açıkça çağırır)
public Kopek(string ad, string cins) : base(ad)
{
Console.WriteLine("Kopek(ad, cins) yapıcısı");
this.Cins = cins;
}
}
// Kullanım
Kopek k = new Kopek("Fındık", "Poodle");
// Çıktı:
// Hayvan(ad) yapıcısı
// Kopek(ad, cins) yapıcısı
Yapıcıların çalışma sırası her zaman üst sınıftan alt sınıfa doğrudur. base(…) çağrısı, alt sınıf yapıcısının gövdesi çalıştırılmadan önce tamamlanır.
Bölüm 4: Metot Geçersiz Kılma (Method Overriding) — Davranışı Özelleştirme
Kalıtımın en güçlü yönlerinden biri, alt sınıfların üst sınıftan miras aldığı davranışları (metotları) özelleştirmesine veya tamamen değiştirmesine olanak tanımasıdır. Bu, Çok Biçimliliğin (Polymorphism) temelini oluşturur.
Anahtar Kelimeler:
virtual: Üst sınıftaki bir metotun (veya özelliğin) alt sınıflar tarafından geçersiz kılınabileceğini (override edilebileceğini) belirtir. virtual metotların bir gövdesi (varsayılan uygulaması) olmak zorundadır.
override: Alt sınıftaki bir metodun, üst sınıftaki virtual veya abstract bir metodu geçersiz kıldığını belirtir. override edilen metodun imzası (adı, parametre türleri ve sırası, dönüş türü — dönüş türü kovaryant olabilir) üst sınıftaki metotla aynı olmalıdır.
abstract: Üst sınıftaki bir metodun (veya özelliğin) gövdesi olmadığını ve alt sınıflar tarafından override edilmesinin zorunlu olduğunu belirtir. abstract üyeler sadece abstract sınıflarda tanımlanabilir ve örtük olarak virtual’dırlar.
sealed: override edilen bir metodun (veya özelliğin) daha alt sınıflar tarafından tekrar geçersiz kılınmasını engellemek için kullanılır (override sealed …). Ayrıca bir sınıfın sealed olması, o sınıftan hiç miras alınamayacağı anlamına gelir.
Çalışma Şekli:
Üst sınıfta bir metot virtual (veya abstract) olarak işaretlenir.
Alt sınıf, aynı imzaya sahip bir metodu override anahtar kelimesiyle tanımlar.
Üst sınıf türündeki bir referans, alt sınıf örneğini tutarken, bu metot çağrıldığında, çalışma zamanında nesnenin gerçek türüne bakılır ve alt sınıfın override ettiği metot çalıştırılır. Eğer alt sınıf metodu override etmemişse (sadece virtual durumunda mümkündür), üst sınıftaki virtual metot çalışır.
Örnek (Yukarıdaki Hayvan/Kopek/Kedi örneğinden):
public class Hayvan
{
// ...
public virtual void SesCikar() // Sanal metot
{
Console.WriteLine("Hayvan sesi...");
}
}
public class Kopek : Hayvan
{
// ...
public override void SesCikar() // Geçersiz kılma
{
Console.WriteLine($"{Ad} havlıyor: Hav hav!");
}
}
public class Kedi : Hayvan
{
// ...
public override void SesCikar() // Geçersiz kılma
{
Console.WriteLine($"{Ad} miyavlıyor: Miyav!");
}
}
// Kullanım (Polimorfizm)
Hayvan h1 = new Kopek("Max", 5, "Golden");
Hayvan h2 = new Kedi("Luna", 1);
Hayvan h3 = new Hayvan("Generic", 10); // Üst sınıf örneği
h1.SesCikar(); // Max havlıyor: Hav hav! (Kopek'in override ettiği çalışır)
h2.SesCikar(); // Luna miyavlıyor: Miyav! (Kedi'nin override ettiği çalışır)
h3.SesCikar(); // Hayvan sesi... (Hayvan'ın virtual metodu çalışır)
Bölüm 5: Metot Gizleme (new Anahtar Kelimesi)
Bir alt sınıf, üst sınıftaki bir metotla aynı imzaya sahip yeni bir metot tanımlayabilir, ancak override yerine new anahtar kelimesini kullanabilir (veya hiçbir şey kullanmazsa derleyici uyarır ama new varsayar). Bu duruma metot gizleme (method hiding) denir.
Farkı: Metot gizleme, metot geçersiz kılmadan (override) farklıdır. new ile tanımlanan metot, üst sınıftaki metodu geçersiz kılmaz, sadece gizler.
Davranış:
Eğer referans alt sınıf türündeyse, alt sınıfın new ile tanımladığı metot çağrılır.
Eğer referans üst sınıf türündeyse (alt sınıf nesnesini tutsa bile), üst sınıfın orijinal metodu çağrılır. Polimorfizm çalışmaz.
Örnek:
public class A
{
public void Metot1() { Console.WriteLine("A.Metot1"); }
public virtual void Metot2() { Console.WriteLine("A.Metot2 (Virtual)"); }
}
public class B : A
{
// Metot1'i gizleme (new ile)
public new void Metot1() { Console.WriteLine("B.Metot1 (Hiding)"); }
// Metot2'yi geçersiz kılma (override ile)
public override void Metot2() { Console.WriteLine("B.Metot2 (Overriding)"); }
}
// Kullanım
B bNesnesi = new B();
A aReferansi = bNesnesi; // Üst sınıf referansı
bNesnesi.Metot1(); // B.Metot1 (Hiding) - Referans B türünde
aReferansi.Metot1(); // A.Metot1 - Referans A türünde, gizlenen değil üstteki çağrılır!
bNesnesi.Metot2(); // B.Metot2 (Overriding) - Referans B türünde
aReferansi.Metot2(); // B.Metot2 (Overriding) - Referans A türünde, AMA override edildiği için B'deki çalışır (Polimorfizm)!
Kural: Metot gizleme (new) genellikle kafa karıştırıcıdır ve polimorfik davranış istenen durumlarda kullanılmamalıdır. Davranışı özelleştirmek için virtual ve override kullanılmalıdır. new sadece, üst sınıftaki metot virtual değilse ve alt sınıfta aynı isimde bir metot tanımlama zorunluluğu (veya isteği) varsa, derleyici uyarısını susturmak için nadiren kullanılır.
Bölüm 6: Kalıtımın Avantajları ve Dezavantajları
Avantajları:
Kod tekrarını azaltır.
Mantıksal hiyerarşi kurar.
Kodun yeniden kullanılabilirliğini artırır.
Genişletilebilirliği kolaylaştırır.
Çok biçimliliği (polymorphism) mümkün kılar.
Bakımı kolaylaştırır (ortak kod tek yerde).
Dezavantajları:
Sıkı Bağlılık (Tight Coupling): Alt sınıflar, üst sınıfların uygulamasına sıkı bir şekilde bağlanabilir. Üst sınıfta yapılan bir değişiklik (özellikle protected üyelere veya virtual metotların davranışına yönelik), alt sınıfları beklenmedik şekillerde etkileyebilir veya kırabilir (“Kırılgan Temel Sınıf Problemi” — Fragile Base Class Problem).
Tekli Kalıtım Sınırlaması: C#’ta sadece bir sınıftan miras alınabilmesi, bazen tasarım esnekliğini kısıtlayabilir (bu durumda arabirimler devreye girer).
Hiyerarşi Derinliği: Çok derin kalıtım hiyerarşileri (A->B->C->D…) kodun anlaşılmasını ve takip edilmesini zorlaştırabilir.
Yanlış Kullanım Riski: Kalıtım, sadece kod paylaşımı için değil, mantıksal bir “bir türüdür” ilişkisi olduğunda kullanılmalıdır. Yanlış kullanıldığında kafa karıştırıcı ve bakımı zor tasarımlara yol açabilir.
Modern Yaklaşım: Kalıtım Yerine Kompozisyon (Composition over Inheritance)
OOP tasarım prensiplerinden biri, mümkün olduğunda kalıtım yerine kompozisyonu tercih etmektir.
Kalıtım: “Bir türüdür” ilişkisi (Kedi bir Hayvan’dır). Sıkı bağlılık yaratır.
Kompozisyon: “Sahiptir” veya “kullanır” ilişkisi (Araba bir Motora sahiptir). Bir sınıf, başka sınıfların nesnelerini kendi içinde üye olarak tutar ve onların işlevselliğini kullanır (delegasyon). Daha esnek ve daha gevşek bağlı bir yapı sunar.
// Kompozisyon örneği
public interface ILogger { void Log(string msg); }
public class DosyaLogger : ILogger { public void Log(string msg) { /.../ } }
public class VeritabaniLogger : ILogger { public void Log(string msg) { /.../ } }
public class RaporlamaServisi
{
private readonly ILogger _logger; // RaporlamaServisi bir ILogger'a "sahiptir/kullanır"
// Logger bağımlılığı dışarıdan enjekte edilir (Dependency Injection)
public RaporlamaServisi(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public void RaporOlustur()
{
_logger.Log("Rapor oluşturma başladı.");
// ... rapor oluşturma ...
_logger.Log("Rapor oluşturma bitti.");
}
}
// Kullanım
ILogger dosyaLog = new DosyaLogger();
RaporlamaServisi servis1 = new RaporlamaServisi(dosyaLog);
servis1.RaporOlustur(); // Dosyaya loglar
ILogger dbLog = new VeritabaniLogger();
RaporlamaServisi servis2 = new RaporlamaServisi(dbLog);
servis2.RaporOlustur(); // Veritabanına loglar
Burada RaporlamaServisi, loglama işlevini miras almak yerine, bir ILogger arabirimini uygulayan bir nesneyi kullanır. Bu sayede loglama mekanizması ( DosyaLogger, VeritabaniLogger ) kolayca değiştirilebilir (gevşek bağlılık).
Bölüm 7: Sonuç — Güçlü Bir OOP Aracı
Kalıtım, C#’ta Nesne Yönelimli Programlamanın temel direklerinden biridir. Kodun yeniden kullanılmasını sağlayarak, mantıksal hiyerarşiler oluşturarak ve çok biçimliliğe zemin hazırlayarak geliştiricilere önemli avantajlar sunar. : söz dizimi ile kolayca uygulanır ve public, protected gibi erişim belirleyiciler miras alınan üyelere erişimi kontrol eder.
Yapıcıların kalıtımdaki rolü (base()) ve metotları geçersiz kılma (virtual, override) veya gizleme (new) mekanizmalarını anlamak, kalıtımı doğru ve etkili bir şekilde kullanmak için kritiktir. Soyut sınıflar, kalıtım hiyerarşilerinde ortak bir temel ve zorunlu uygulamalar tanımlamak için kullanılırken, arabirimler daha çok yetenek bazlı sözleşmeler ve çoklu “kalıtım” senaryoları için tercih edilir.
Ancak kalıtımın getirdiği sıkı bağlılık riskleri de göz önünde bulundurulmalı ve modern tasarım prensiplerinde genellikle “kalıtım yerine kompozisyonu tercih etme” eğilimi vardır. Yine de, doğru kullanıldığında kalıtım, C#’ta zarif, organize ve genişletilebilir kodlar yazmak için güçlü ve vazgeçilmez bir araçtır. Bu mekanizmaya hakim olmak, OOP prensiplerini tam olarak anlamanın ve uygulamanın anahtarıdır.