Bir metodun hangi sınıfa ait olduğu bazen göründüğü kadar net değil. C#'ta polimorfizm çoğu zaman beklendiği gibi çalışır, ama bazı durumlarda tam tersi olur ve bu hatalar genelde code review'a kadar fark edilmeden ilerler.
Aşağıda dört örnek var.
Hepsinde "hangi metot çalışır" sorusunun cevabı beklenenden farklı çıkıyor.
Sebebini örneklerden sonra birlikte açacağız.
1. Constructor İçinden Metot Çağırmak
public class MeyveSepeti
{
public MeyveSepeti() => Hazirla();
protected virtual void Hazirla() => Console.WriteLine("Sepet hazırlanıyor");
}
public class ElmaSepeti : MeyveSepeti
{
private readonly string _meyveAdi = "Elma";
public ElmaSepeti(string meyveAdi) { _meyveAdi = meyveAdi; }
protected override void Hazirla() => Console.WriteLine($"Sepetten {_meyveAdi} alınıyor");
}
var sepet = new ElmaSepeti("Armut");
// Çıktı: Sepetten alınıyor (meyve adı boş çıkıyor)
Beklenen çıktı "Sepetten Armut alınıyor" gibi görünüyor, ama öyle olmuyor.
Çağrılan metot ElmaSepeti'nin metodu oluyor, doğru. Ama o an _meyveAdi henüz hiç değer almamış, adı boş çıkıyor.
Roslyn (C#'ın kod analizi yapan derleyici altyapısı) bu durumu ayrı bir kuralla işaretliyor: CA2214. Kural, constructor içinde override edilebilir bir metot çağrıldığında uyarı veriyor. Projede açık değilse fark edilmeden geçebiliyor.
2. Aynı Sınıftaki Bir Metodun Başka Bir Metodu Çağırması
public class MeyveSepeti
{
public virtual void SesVer() => Console.WriteLine("Sepetten ses geliyor");
public void Duyur() => this.SesVer();
}
public class ElmaSepeti : MeyveSepeti
{
public override void SesVer() => Console.WriteLine("Elma sepetinden ses geliyor");
}
ElmaSepeti sepet = new ElmaSepeti();
sepet.Duyur();
// Çıktı: Elma sepetinden ses geliyor
Duyur() metodu MeyveSepeti'de yazılı.
Ama çalıştığında ElmaSepeti'nin metodunu çağırıyor.
3. new ile Metot Gizleme
public class MeyveSepeti
{
public void Tanit() => Console.WriteLine("Bu bir meyve sepeti");
}
public class ArmutSepeti : MeyveSepeti
{
public new void Tanit() => Console.WriteLine("Bu bir armut sepeti");
}
MeyveSepeti sepet = new ArmutSepeti();
sepet.Tanit();
// Çıktı: Bu bir meyve sepeti
Nesne gerçekte bir ArmutSepeti.
Ama çıktı "Bu bir armut sepeti" değil, "Bu bir meyve sepeti" oluyor.
4. Sadece Interface Üzerinden Görünen Metot
public interface IKapasiteHesaplanabilir
{
double Kapasite();
}
public class MeyveSepeti : IKapasiteHesaplanabilir
{
private readonly double _boyut;
public MeyveSepeti(double boyut) => _boyut = boyut;
double IKapasiteHesaplanabilir.Kapasite() => _boyut * _boyut;
}
var sepet = new MeyveSepeti(4);
// sepet.Kapasite(); -> derleme hatası
IKapasiteHesaplanabilir kapasiteli = sepet;
kapasiteli.Kapasite();
// Çıktı: 16
sepet.Kapasite() hiç çalışmıyor, derleme hatası veriyor.
Aynı metot, kapasiteli.Kapasite() olarak yazınca çalışıyor.
Peki Bu Dördünün Sebebi Ne?
Dört örnek de aynı sebepten değil, iki ayrı sebepten kaynaklanıyor.
Birinci sebep: nesne oluşurken ayarlanan bir bilgi
new ElmaSepeti(...) çalıştığında sırayla şunlar olur:
Nesne için bellekte yer ayrılır.
Bu nesneye, "sen bir ElmaSepeti'sin" bilgisi hemen o anda iliştirilir. Bu bilgi, henüz hiçbir constructor çalışmadan hazır olur.
Sonra MeyveSepeti'nin constructor'ı çalışır.
Bu constructor içinde bir metot çağrıldığında, çalışma zamanı nesneye iliştirilmiş olan bu bilgiye bakar ve "bu aslında bir ElmaSepeti, o zaman ElmaSepeti'nin metodunu çalıştır" der.
MeyveSepeti'nin constructor'ı bittikten sonra ElmaSepeti'nin kendi alanları değer alır.
En son ElmaSepeti'nin constructor gövdesi çalışır.
örnekte sorun burada: 4. adım, 5. adımdan önce oluyor. Metot doğru sınıfa ait ama alan henüz boş.
örnekte de aynı 4. adım işliyor, ama bu sefer nesne tam kurulmuş durumda, bu yüzden sonuç aslında doğru. Sürpriz olan tek şey, MeyveSepeti içinde yazılan bir kodun ElmaSepeti'nin metodunu çalıştırabilmesi.
İkinci sebep: derleyicinin önceden verdiği karar
3 ve 4. örnekler bambaşka bir yerden geliyor. Burada çalışma zamanı hiçbir şeye bakmıyor.
Kod daha derlenirken, derleyici değişkenin hangi tipte tanımlandığına bakıyor ve çağrının nereye gideceğine orada karar veriyor.
örnekte sepet değişkeni MeyveSepeti tipinde tanımlı. Derleyici bu yüzden çağrıyı MeyveSepeti'nin metoduna bağlıyor, nesnenin gerçekte ArmutSepeti olması hiç önemli değil.
örnekte ise Kapasite() metodu, MeyveSepeti'nin normal bir üyesi olarak bile tanımlanmamış. Derleyici sepet.Kapasite() yazıldığında bu metodu MeyveSepeti'nin listesinde bulamıyor, hata veriyor.
Özetle: 1 ve 2, nesneye iliştirilen gerçek tip bilgisinden kaynaklanıyor. 3 ve 4 ise derleyicinin değişkenin tanımlı tipine bakarak verdiği erken karardan.
Kaçınma Yolları
Constructor içinde başka bir metot çağırmaktan kaçınmak. Bunun yerine nesneyi ayrı bir metotla, tam kurulduktan sonra hazırlamak.
Bir sınıfın kendi metodu başka bir metodunu çağırdığında, bunun alt sınıfta değişebileceğini akılda tutmak.
overridevenewarasındaki farkı her zaman açıkça yazmak.Interface üzerinden yazılan bir metodu çağırırken hangi tipteki değişkeni kullandığına dikkat etmek.
CA2214 kuralını projede açık tutmak.
Sonuç
Dört örnek de "hangi metot çalışır" sorusunun cevabının, göründüğünden daha karmaşık olabileceğini gösteriyor.
Bazen bu cevap nesneye iliştirilen gerçek tip bilgisine bağlı, bazen de derleyicinin daha en baştan verdiği bir karara.

Top comments (0)