Skip to content

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ı, new ile 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ı

  1. BankaHesabi sı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), static HesapSayisi (her constructor'da artar).
  2. İ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.
  3. İ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ı.
  4. 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.
  5. Geçen dersin oop-oncesi projesini aç ve programı Ogrenci sı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.