Bölüm 10 / 15
Sınıf ve nesne
field, property, constructor, this, static.
9 dakikalık okuma
Geçen ders fikri kurdu; bu ders aleti veriyor. İlk sınıfını
yazacak, nesne yaratacak ve sınıf tasarımının beş yapı taşını öğreneceksin: field,
property, constructor, this ve static. Ders bittiğinde geçen
dersin paralel dizili programını sınıfla yeniden yazmış ve iki sürümü yan yana ölçmüş
olacaksın.
01Ne öğreneceksin
- Sınıf tanımı,
newile nesne yaratma; nesnenin referans tipi olduğu gerçeğinin günlük sonuçları - Field ile property farkı; auto-property, doğrulamalı set, get-only
- Constructor: nesneyi geçerli doğmaya zorlamak; object initializer;
this static: sınıfa ait üye ile örneğe ait üyenin ayrımı — ve Console.WriteLine'ın sırrı
02İlk sınıf: tanım, new, field
Sınıf class anahtar kelimesiyle tanımlanır; içindeki değişkenlere
field (alan) denir. Nesne new ile yaratılır, üyelere nokta ile
erişilir:
class Ogrenci
{
public string Ad; // field: nesnenin verisi
public int Not;
}
var aysel = new Ogrenci(); // new: bellekte yeni bir Ogrenci kur, adresini ver
aysel.Ad = "Aysel";
aysel.Not = 85;
var deniz = new Ogrenci(); // ikinci nesne: verisi tamamen ayrı
deniz.Ad = "Deniz";
deniz.Not = 42;
Console.WriteLine(aysel.Not); // 85 — deniz'in 42'si onu etkilemez
Sınıf tanımı, üst düzey satırlarının ve metotlarının altına yazılır
(derleyici kuralı: top-level statements dosyada tip tanımlarından önce gelir).
public kelimesi "bu üyeye dışarıdan erişilebilir" der; erişim
belirleyicilerin tam ailesi kapsülleme dersinde (11. ders) — bugün her şeyi public
yazıp bir sonraki derste neden yanlış olduğunu göreceğiz.
Kritik bağlantı: sınıflar referans tipidir — dizide (6. ders) öğrendiğin her
şey nesnelerde de geçerlidir. var b = aysel; ikinci nesne yaratmaz, aynı
nesneye ikinci isim verir; b.Not = 0 yazarsan aysel.Not da
0'dır. Metoda nesne geçmek adresi kopyalar: metot nesnenin içini değiştirebilir.
Yeni ve bağımsız nesne yalnızca new ile doğar.
03Property: alanın önündeki kapı
Yukarıdaki public field'ların sorunu geçen dersten tanıdık:
aysel.Not = -50; yazan hiçbir kodu derleyici durduramaz — kuralı koruyan
yok. C#'ın çözümü property'dir: dışarıdan field gibi görünen ama okuma
(get) ve yazma (set) anlarına kod koyabilen kontrollü
kapı:
class Ogrenci
{
private int not; // asıl veri: artık dışarıya kapalı (private)
public int Not
{
get { return not; }
set
{
if (value < 0 || value > 100) // value: atanmak istenen değer
{
return; // geçersizi sessizce reddet (şimdilik)
}
not = value;
}
}
}
var aysel = new Ogrenci();
aysel.Not = 85; // set çalışır, kural denetler, kabul eder
aysel.Not = -50; // set çalışır, kural reddeder
Console.WriteLine(aysel.Not); // 85 — geçersiz değer içeri hiç giremedi
Kullanan taraf için sözdizimi field ile aynıdır (aysel.Not = 85);
fark perde arkasındadır: her atama set bloğundan, her okuma
get'ten geçer. "Geçersizi sessizce reddetmek" de en iyi tepki değildir —
doğrusu hata fırlatmaktır ve Hat 03'ün exception dersinde bu örneğe dönülecek.
Her property'ye kural gerekmez; kuralsız olanlar için kısa yazım vardır — auto-property — ve C# kodunun standart görünümü budur:
class Ogrenci
{
public string Ad { get; set; } // arka alanı derleyici üretir
public DateTime KayitTarihi { get; } // get-only: sonradan değiştirilemez
}
Meslek kuralı: veriyi dışarıya her zaman property ile aç; public field yazma. Bugün kural gerekmese bile yarın gerekir; property'yse dışarıdaki kod hiç değişmeden set'ine kural eklenir — field'ı property'ye çevirmek ise onu kullanan tüm kodu yeniden derlemeyi gerektiren bir imza değişikliğidir. Framework'lerin (EF Core, model binding — Hat 5 ve 7) çalıştığı üyeler de property'lerdir.
04Constructor: geçerli doğum
Şu anki sınıfın açığı: new Ogrenci() ile adsız, notsuz, "yarım" nesne
yaratılabiliyor. Constructor (kurucu), new anında çalışan özel
metottur: sınıfla aynı adı taşır, dönüş tipi yazılmaz ve nesnenin geçerli doğması için
istediklerini zorunlu kılar:
class Ogrenci
{
public string Ad { get; set; }
public int Not { get; set; }
public DateTime KayitTarihi { get; }
public Ogrenci(string ad, int baslangicNotu)
{
if (string.IsNullOrWhiteSpace(ad)) // doğum anında guard clause (8. ders)
{
ad = "İsimsiz";
}
Ad = ad;
Not = baslangicNotu;
KayitTarihi = DateTime.Now; // get-only property'ye yalnızca burada yazılabilir
}
}
var aysel = new Ogrenci("Aysel", 85); // parametresiz new Ogrenci() ARTIK DERLENMEZ
var deniz = new Ogrenci("Deniz", 42);
İki kural: sen hiç constructor yazmazsan derleyici görünmez, parametresiz bir tane
üretir (ilk örneklerin bu yüzden çalıştı); bir tane bile yazarsan bu hediye
kalkar — nesne artık yalnızca senin tanımladığın kapılardan doğar. Constructor da
overload edilir (8. dersin kuralları aynen): Ogrenci(string ad) ve
Ogrenci(string ad, int not) yan yana yaşayabilir.
Bir de kısa yol tanı — property'leri new anında dolduran object initializer:
var burak = new Ogrenci("Burak", 90) { Not = 95 }; // önce constructor, sonra atamalar
var bos = new Ogrenci { Ad = "Ece" }; // yalnızca parametresiz constructor varsa çalışır
Seçim ölçütü: nesnenin onsuz geçersiz olduğu veriler constructor
parametresidir; isteğe bağlı olanlar initializer'a bırakılabilir. "Her şey initializer,
constructor boş" düzeni, yarım nesne sorununu geri getirir — 6. dersteki
new string[3] null tuzağının sınıf düzeyindeki karşılığıdır.
05this: nesnenin kendisi
this, üyenin içinde "şu an üzerinde çalışılan nesne" demektir. En sık
iki yerde görülür — ad çakışmasını çözmek ve constructor'ları zincirlemek:
class Ogrenci
{
public string Ad { get; set; }
public Ogrenci(string ad)
{
Ad = ad; // sorun yok: property PascalCase, parametre camelCase
}
public Ogrenci() : this("İsimsiz") // constructor zinciri: işi asıl kurucuya devret
{
}
public bool AyniKisiMi(Ogrenci diger)
{
return this.Ad == diger.Ad; // this: kendi Ad'ım, diger: parametreninki
}
}
C# adlandırma geleneği (property PascalCase, parametre camelCase) çakışmaların
çoğunu zaten önler; this.Ad yazımı bu yüzden mecburiyetten çok netlik
tercihi olarak görünür. Zincirleme (: this(...)) ise overload'lu
kurucularda başlatma kodunun tek yerde toplanmasını sağlar — DRY, kurucularda da
geçerli.
06static: nesneye değil sınıfa ait
Şimdiye dek her üye örneğe aitti: her nesnenin kendi Not'u var.
Bazen ise bilgi tek tek nesnelerin değil sınıfın kendisinin malıdır —
static bunu söyler:
class Ogrenci
{
public static int ToplamKayit; // sınıfın tamamı için TEK göz
public string Ad { get; set; }
public Ogrenci(string ad)
{
Ad = ad;
ToplamKayit++; // her doğumda ortak sayaç artar
}
}
var a = new Ogrenci("Aysel");
var d = new Ogrenci("Deniz");
Console.WriteLine(Ogrenci.ToplamKayit); // 2 — nesne üzerinden değil, SINIF üzerinden
Console.WriteLine(a.ToplamKayit); // DERLEME HATASI: static üyeye örnekten erişilmez
Erişim sözdizimi ayrımı da öğreticidir: örnek üyeye nesne.Uye, static
üyeye Sinif.Uye. Şimdi iki dersin sırrı çözülüyor:
Console.WriteLine, DateTime.Now, int.TryParse,
Array.Sort, string.Join — hepsinde adın sınıf olması
bundandır: bunlar static üyelerdir, nesne yaratmadan çağrılırlar. Metotlar da static
olabilir; kuralı basittir: metot nesnenin verisini kullanıyorsa örnek metodu,
kullanmıyorsa static olabilir. 8. derste yazdığın bağımsız yardımcı metotlar
kavramsal olarak static'ti.
Uyarısını da ilk günden al: static paylaşılan durum demektir — programın her yeri aynı gözü okur ve yazar. Sayaç gibi masum örneklerde sorun çıkmaz; ama "kolay erişim olsun" diye veriyi static'e koymak, kimin ne zaman değiştirdiği izlenemeyen küresel değişken üretir ve test edilebilirliği öldürür (Hat 15'te bu cümlenin gerekçesini test yazarak yaşayacaksın). İlke: static'i sayaç, sabit ve yardımcı metot için kullan; iş verisini nesnelerde tut.
07Dönüşüm: paralel dizilerden sınıfa
Geçen dersin alıştırmasındaki program, bu dersin araçlarıyla şöyle görünür:
var ogrenciler = new List<Ogrenci>
{
new Ogrenci("Aysel", 85),
new Ogrenci("Deniz", 42),
new Ogrenci("Burak", 90)
};
Listele(ogrenciler);
Console.WriteLine($"Ortalama: {Ortalama(ogrenciler):0.00}");
void Listele(List<Ogrenci> liste)
{
foreach (var o in liste)
{
Console.WriteLine($"{o.Ad} — {o.Not} — {(o.GectiMi() ? "geçti" : "kaldı")}");
}
}
double Ortalama(List<Ogrenci> liste)
{
if (liste == null || liste.Count == 0) { return 0; }
var toplam = 0;
foreach (var o in liste) { toplam += o.Not; }
return (double)toplam / liste.Count;
}
Geçen dersin iki ölçümünü hatırla ve karşılaştır: yeni alan eklemek artık yalnızca
sınıf tanımına ve onu gösteren satıra dokunur — Listele'nin imzası
değişmez. Kayıt silmek tek listeden tek nesne silmektir — "kayma" diye bir hata sınıfı
artık yazılamaz hale geldi. İyi tasarımın ölçüsü budur: hatayı yakalamak değil,
hatanın yazılabileceği zemini ortadan kaldırmak.
08Mini alıştırma — BankaHesabi
Yaklaşık 25 dakika · dotnet new console -o banka · bu hattın en önemli alıştırması
BankaHesabisınıfını yaz:Sahip(get-only, constructor'da atanır),Bakiye(decimal — 2. dersin kuralı; get public, set private:public decimal Bakiye { get; private set; }— dışarıdan okunur ama yazılamaz), staticHesapSayisi(her constructor'da artar).- İki metot:
void ParaYatir(decimal tutar)— tutar 0 ya da negatifse guard clause ile hiçbir şey yapmadan çık, değilse bakiyeye ekle;bool ParaCek(decimal tutar)— tutar geçersizse ya da bakiyeden büyükse false, değilse düş ve true. - İki hesap aç, para yatır/çek, sonuçları ve
BankaHesabi.HesapSayisi'nı yazdır. Sonra kötü niyetli kodu dene:hesap.Bakiye = 1000000;— derleyicinin seni durdurduğunu gör. Geçen dersteki "kural kim korur?" sorusunun cevabı artık kodda: derleyici korur, çünkü sınıf öyle tasarlandı. - Referans deneyi:
var ortak = hesap1;yap,ortaküzerinden para çek,hesap1.Bakiye'nin değiştiğini gör — nesnelerin referans tipi olduğunu banka örneğinde de doğrula. - Geçen dersin
oop-oncesiprojesini aç ve programıOgrencisınıfıyla yeniden yaz (07. bölümdeki iskelet). kayitYili alanını şimdi ekle ve geçen ders kaç satıra dokunduğunu, bu kez kaça dokunduğunu yan yana not et.
09Kendini sına
Field ile property arasındaki fark nedir? Neden "public field yazma" kuralı var?
Field verinin kendisidir; property, okuma (get) ve yazma (set) anlarına kod konabilen kontrollü kapıdır — kullanan için sözdizimi aynıdır. Public field'da kural koyacak yer yoktur; property'de set'e doğrulama sonradan, dışarıdaki kodu hiç değiştirmeden eklenir. Ayrıca framework'ler (EF Core, model binding) property'lerle çalışır. Bu yüzden veri dışarıya property ile açılır; kuralsızsa auto-property yeter.
Kendi constructor'ını yazmak, parametresiz new'i neden ortadan kaldırır? Bu neden istenen bir davranıştır?
Hiç constructor yazılmazsa derleyici parametresiz bir tane hediye eder; bir tane bile yazılırsa hediye kalkar — nesne yalnızca tanımlanan kurucularla doğar. Bu istenen davranıştır çünkü constructor, nesnenin geçersiz (yarım) doğmasını engelleyen kapıdır: zorunlu veriler parametreyle alınır, doğum anında doğrulanır. Parametresiz new gerekiyorsa bilinçli olarak overload yazılır.
static üye ile örnek üyesi arasındaki fark nedir? Console.WriteLine'ı nesne yaratmadan çağırabilmemizin sebebi nedir?
Örnek üyesi her nesnede ayrı vardır ve nesne.Uye
ile erişilir; static üye sınıfın tamamı için tek gözdür ve Sinif.Uye
ile erişilir — örnekten erişim derleme hatasıdır. Console.WriteLine, DateTime.Now,
int.TryParse static üyelerdir; bu yüzden nesnesiz, sınıf adıyla çağrılırlar. static
paylaşılan durum demektir: sayaç/sabit/yardımcı metot için uygundur, iş verisi için
değildir.
var b = a; (a bir Ogrenci) sonrası b.Not = 0; a'yı etkiler mi? Bağımsız kopya nasıl alınır?
Etkiler — sınıflar referans tipidir; atama nesneyi değil adresini kopyalar, a ve b aynı nesnenin iki adıdır. Bağımsız nesne yalnızca new ile doğar: yeni bir Ogrenci yaratıp alanlarını kopyalamak gerekir (bu işin hazır kalıpları — kopyalayan constructor, record'ların with ifadesi — Hat 03'te). Metoda nesne geçmenin de aynı kurala tabi olduğunu unutma: metot, nesnenin içini değiştirebilir.
10Sınav köşesi
Yazılıda nasıl sorulur
Üç kalıp: (1) Kavram eşleme — "Nesne oluşturulurken çalışan özel metot?"
(constructor; sınıfla aynı ad, dönüş tipi yok), "Tüm nesnelerin paylaştığı üye
hangi anahtar kelimeyle tanımlanır?" (static). (2) Çıktı sorusu — static sayaçlı
sınıftan n nesne yaratılıp sayaç yazdırılır; ya da iki değişkenin aynı nesneyi
gösterdiği referans senaryosu. (3) Hata avı — "bir constructor yazılmış sınıfta
new X() neden derlenmez?", "static metottan örnek üyesine erişim"
(derlenmez — static tarafta this yoktur). Property/field ayrımı doğru-yanlış
maddesi olarak da gelir.
Sözlüde nasıl anlatılır — 2 dakikalık taslak
"Bir sınıfı nasıl tasarlarsınız?" sorusuna uygulamalı cevap: (1) Veriyi private tutar, dışarıya property ile açarım; kural gereken set'e doğrulama koyarım — bakiye örneğini ver: private set + ParaYatir/ParaCek metotları, geçersiz durum derleme/doğrulama aşamasında ölür. (2) Zorunlu verileri constructor'da alırım — nesne yarım doğamaz. (3) static'i sayaç ve yardımcılarla sınırlarım; paylaşılan durumun test maliyetini bilirim. (4) Kapanış: "Amacım hatayı yakalamak değil, yazılamaz kılmak — geçersiz durumu temsil edilemez yapan sınıf, en ucuz hata düzeltmesidir." Bu cümle mülakatta seviye belirler.
