Skip to content

Bölüm 2 / 13

Null güvenliği

Nullable tipler, ??, ?., nullable reference types.

9 dakikalık okuma

.NET dünyasının bir numaralı üretim hatası NullReferenceException'dır — log dosyalarının değişmeyen müdavimi. Bu ders null'un ne olduğunu 1. dersin referans modeli üzerinden netleştiriyor, değer tiplerini null'lanabilir yapan Nullable<T>'yi, ??/?. operatör ailesini ve modern C#'ın en büyük güvenlik hamlesi olan nullable reference types'ı işliyor. Hedef, null kontrolünü serpiştirilen bir alışkanlık olmaktan çıkarıp derleyiciyle birlikte kurulan bir sisteme çevirmek.

01Ne öğreneceksin

  • null'un tam anlamı ve NullReferenceException'ın anatomisi
  • Nullable<T> (int?): HasValue/Value, veritabanı NULL'u ile ilişkisi
  • Operatör ailesi: ??, ??=, ?., ?[] ve zincirleme kalıpları
  • Nullable reference types: string?, derleyici akış analizi, ! operatörünün yeri
  • Savunma kalıpları: guard clause, TryParse, "null mı boş koleksiyon mu" API kararı

02null nedir, NullReferenceException nasıl doğar

1. dersin modeliyle tarif çok kısa: referans tipi değişken, heap'teki bir nesnenin referansını taşır; null, o referansın yokluğudur — "bu değişken şu an hiçbir nesneyi göstermiyor" işareti. Sorun null'un varlığı değil, null referans üzerinden üyeye erişme girişimidir: gösterilecek nesne yokken musteri.Ad demek, çalışma zamanında NullReferenceException (NRE) fırlatır. Kavramın mucidi Tony Hoare bunu "milyar dolarlık hatam" diye anar — dilin her referansı sessizce null'lanabilir yapması, elli yıldır üretim çökertiyor.

Musteri musteri = MusteriBul(id);    // bulamazsa null döndürüyor olsun
Console.WriteLine(musteri.Ad);       // id yoksa: NullReferenceException

// .NET 6+ hata mesajı suçluyu söyler, okumayı öğren:
// System.NullReferenceException: Object reference not set to an instance of an object.
//   musteri was null.

İki tespit baştan yerleşsin. Birincisi: NRE fırladığı satır, hatanın belirtisidir; kökü, null'un o değişkene atandığı ya da hiç atanmadığı yerdir — ayıklarken "bu null buraya nereden geldi?" diye geriye yürürsün. İkincisi: değer tipleri null olamaz (1. ders: veri, referans değil) — bu yüzden "değeri yok" durumu değer tipleri için ayrı bir mekanizma ister. Sıradaki bölüm tam o mekanizma.

03Nullable<T>: değer tipine "yok" hâli eklemek

Bir formda "doğum yılı" alanı boş bırakılabiliyorsa int yetmez — 0 gerçek bir cevap mı, boşluk mu, ayıramazsın. Veritabanı da aynı derdi NULL ile çözmüştü (Hat 02'nin 14. dersi); C# tarafındaki karşılık Nullable<T>, kısa yazımıyla T?: değerin yanına bir "var/yok" bayrağı ekleyen küçük bir struct.

int? dogumYili = null;               // değer tipi ama "yok" hâli var
if (dogumYili.HasValue)
    Console.WriteLine(dogumYili.Value);

int yil = dogumYili ?? 0;            // yoksa varsayılan — bir sonraki bölümün operatörü
int patlar = dogumYili.Value;        // değer yokken .Value: InvalidOperationException!
  • HasValue bayrağı ve Value erişimi ikilinin çekirdeği; değer yokken Value okumak NRE değil InvalidOperationException fırlatır — sınavların ayrıntı sorusu.
  • GetValueOrDefault() değer yoksa default döndürür, fırlatmaz.
  • Null'lanabilir değerle aritmetik "yayılır": null + 5 → null. Karşılaştırmada dikkat: dogumYili > 1990, değer null ise false'tur — "bilinmiyor" hiçbir eşitsizliği kazanamaz (veritabanının üç değerli mantığının yumuşatılmış akrabası).
  • EF Core (Hat 07) eşlemesi birebir: tablodaki NULL'lanabilir sütun, C# tarafında T? olur — iki dünyanın "yok" kavramı aynı hizaya gelir.

04Operatör ailesi: null'la kısa ve güvenli konuşmak

Null kontrolünün if (x != null) ile yazılan hantal hâlini dil dört operatörle sıkıştırır:

OperatörAdıNe yapar
a ?? bnull birleştirmea null değilse a, null ise b
a ??= bnull birleştirmeli atamaa null ise a'ya b ata (tembel varsayılan)
a?.Uyenull koşullu erişima null ise erişme, ifadenin tümü null olsun
a?[i]null koşullu indeksaynı davranışın indeksleyici hâli
// zincirleme: ara halkalardan biri null ise ifade null'a "kısa devre" yapar
string? sehirAd = siparis?.Musteri?.Adres?.Sehir;

// yaygın ikili kalıp: güvenli eriş + varsayılana bağla
string ad = musteri?.Ad ?? "(isimsiz)";

// koleksiyon boş MU null MU ayrımını tek satırda yönet
int adet = sepet?.Urunler?.Count ?? 0;

// event çağırma deseni — 7. derste yerine oturacak
Degisti?.Invoke(this, EventArgs.Empty);

İki incelik: ?. zinciri değer tipiyle bitiyorsa sonuç T?'ye döner (sepet?.Urunler?.Count bir int?) — çünkü "erişilemedi" hâli taşınmak zorunda. Ve null testinin modern yazımı is null / is not null'dur: == tip tarafından aşırı yüklenebilirken (1. ders), is null her zaman saf referans kontrolüdür — kütüphane kodunda tercih nedeni budur.

05Nullable reference types: derleyiciyi bekçi yapmak

Buraya kadarki araçlar null'la başa çıkmayı kolaylaştırır; asıl hamle null'u derleme zamanında yakalamaktır. C# 8'in nullable reference types (NRT) özelliği açıkken — modern proje şablonlarında <Nullable>enable</Nullable> ile varsayılan açıktır — referans tiplerinde de sözleşme ikiye ayrılır:

string ad = null;        // UYARI CS8600: 'string' artık "null olmaz" demek
string? not = null;      // meşru: soru işareti "null olabilir" sözleşmesi

int Uzunluk(string? s)
{
    // return s.Length;          // UYARI: s null olabilir
    if (s is null) return 0;
    return s.Length;             // uyarı yok — akış analizi s'in artık null
}                                // olamayacağını KANITLADI

Mekanizmanın adı akış analizidir: derleyici her satırda "bu ifade şu an null olabilir mi?" bilgisini taşır; kontrol ettiğin dalda uyarı susar. Üç kritik gerçeği mülakat düzeyinde bil:

  • NRT bir derleme zamanı sözleşmesidir, çalışma zamanı garantisi değildir. string yazmak CLR'de tipi değiştirmez; NRT kapalı bir kütüphaneden, yansımadan ya da serileştiriciden yine null gelebilir. Uyarısız kod "NRE imkânsız" demek değildir — "derleyicinin gördüğü yollar temiz" demektir.
  • ! (null bağışlama) operatörü derleyiciye "sustur, bunun null olmadığını ben biliyorum" der: musteri!.Ad. Meşru kullanımı dardır (derleyicinin göremediği bir garanti — örneğin hemen üstte doldurulan test nesnesi); her uyarıya refleks olarak ! basmak, sigortayı bantlamaktır — kod incelemelerinde işaretlenir.
  • Eski projelerde geçiş kademeli yapılır: #nullable enable yönergesiyle dosya dosya açılır.

06Savunma kalıpları: null'u tasarımla azaltmak

Operatörler semptomu yönetir; kıdemli refleks null'un dolaşıma girmesini azaltır. Dört kalıp:

  • Guard clause — sınırda doğrula: public metodun girişinde argümanı kontrol et, geçersizse hemen fırlat. Modern kısayolu: ArgumentNullException.ThrowIfNull(musteri); — tek satır, parametre adını da mesaja yazar. Böylece null, katmanlar boyunca sürüklenip alakasız bir yerde patlamaz; hata kaynağında yakalanır (4. ders bu felsefeyi genelleştirecek).
  • TryParse deseni — başarısızlığı null/exception yerine bool'la anlat: int.TryParse(girdi, out int sayi) hem exception maliyetinden hem null dönüşten kurtarır; "beklenen başarısızlık" için standart .NET kalıbıdır. Kendi metotlarında da uygula: bool TryMusteriBul(int id, out Musteri m).
  • Koleksiyonda asla null döndürme: "sonuç yok" için boş koleksiyon döndür (Array.Empty<T>(), Enumerable.Empty<T>()) — çağıran foreach'e kontrolsüz girebilsin. null koleksiyon, her çağırana kontrol yükü dağıtan bir tasarım hatasıdır.
  • Tekil sonuçta kararı bilinçli ver: "bulunamadı" olağan bir durumsa Musteri? döndür (ada da yansıt: MusteriBulVeyaNull / LINQ'daki FirstOrDefault — 6. ders); olağanüstü bir durumsa exception fırlat (4. ders). Ortası — bazen null bazen exception — en kötüsüdür.

07Sahada null: NRE ayıklama ve API sözleşmesi

Üretimde NRE loguyla karşılaştığında sıra hep aynıdır: stack trace'in en üst satırındaki metoda git; .NET 6+ mesajı hangi ifadenin null olduğunu söyler (musteri was null), oradan null'un kaynağına geriye yürü — atamayı yapan katman mı, hiç doldurulmayan alan mı, sözleşmesi belirsiz bir API mi? Çözüm de üç kademedir: kaynağı düzelt (en iyisi), sınıra guard ekle (kaynağa erişemiyorsan), ?./?? ile yerelde savun (son çare — çünkü null'u maskeler, yanlış varsayılanla sessizce devam edebilir).

API tasarlarken de sözleşmeyi tipe yazdır: NRT açıkken imzadaki her T?, çağırana "null gelebilir, hazırlan" der; T ise "bana null verme, ben de sana vermem" taahhüdüdür. İyi ekiplerde bu sözleşme kod incelemesinin diline girer: "bu parametre gerçekten nullable mı, yoksa üst katman doğrulamayı unutmuş mu?" Hat 05'te ASP.NET model doğrulaması, bu dersin guard mantığını framework'e devredecek — ama tipi doğru bildirmek yine senin işin.

08Mini alıştırma — Uyarıları söndür, sözleşmeyi kur

Yaklaşık 15-20 dakika · dotnet run · NRT açık (varsayılan şablon)

  1. Musteri sınıfı yaz: string Ad, string? Telefon, Adres? Adres (Adres'te string Sehir). Hangi alanı neden nullable yaptığını birer cümle not et — bu, NRT'nin asıl egzersizidir.
  2. Musteri? Bul(int id) metodu yaz (listede arasın, yoksa null). Çağıran tarafta şehri üç biçimde yazdır: önce if (m is not null) bloğuyla, sonra tek satır m?.Adres?.Sehir ?? "(bilinmiyor)" ile, sonra bilerek kontrolsüz erişip derleyici uyarısını gör ve oku.
  3. int? dogumYili ile üç deney: null + 5 sonucu, null > 1990 karşılaştırması, değersizken .Value erişiminin fırlattığı exception'ın tipi. Üçünü de tahmin et → çalıştır → not et.
  4. Guard turu: void Kaydet(Musteri m) metoduna ArgumentNullException.ThrowIfNull(m); ekle, null ile çağırıp hata mesajındaki parametre adını gör. Sonra List<Musteri> Ara(string sehir) yaz — sonuç yoksa null değil boş liste döndür ve çağıran tarafın kontrolsüz foreach'ine dikkat çek.

09Kendini sına

null tam olarak nedir? NullReferenceException hangi anda fırlar — null atamak mı, null okumak mı, üyeye erişmek mi?

null, referans tipi değişkenin hiçbir nesneyi göstermeme hâlidir — referansın yokluğu. Değişkene null atamak da null'u okuyup karşılaştırmak da serbesttir; exception, null referans üzerinden üyeye erişilince (musteri.Ad) fırlar. Ayıklarken fırlayan satır belirtidir; kök neden null'un o değişkene geldiği yerdir, oraya geriye yürünür.

int? nasıl çalışır? Değersizken .Value okumak hangi exception'ı fırlatır ve null içeren karşılaştırmalar neden false döner?

int?, Nullable<int>'in kısaltmasıdır: değerin yanında HasValue bayrağı taşıyan bir struct. Değersizken .Value okumak InvalidOperationException fırlatır (NRE değil). Aritmetik null'u yayar (null+5 = null); sıralı karşılaştırmalar ise değer yokken false döner çünkü "bilinmiyor" hiçbir eşitsizliği sağlayamaz — veritabanındaki NULL mantığının akrabası. Varsayılana bağlamak için ?? ya da GetValueOrDefault kullanılır.

Nullable reference types ne garanti eder, ne etmez? ! operatörü ne zaman meşrudur?

NRT, derleme zamanı sözleşmesidir: string "null olmaz", string? "null olabilir" demektir ve derleyici akış analiziyle kontrolsüz kullanımı uyarır. Çalışma zamanı garantisi yoktur — CLR'de tip aynıdır; NRT kapalı kütüphaneden, yansımadan, serileştiriciden null sızabilir. ! operatörü "null olmadığını ben garanti ediyorum" diyerek uyarıyı susturur; derleyicinin göremediği gerçek bir garanti varsa meşrudur, uyarı susturma refleksi olarak kullanmak sigortayı devre dışı bırakmaktır.

"Bulunamadı" durumunu bir API nasıl bildirmeli? Koleksiyon ve tekil sonuç için kuralları ve gerekçelerini anlat.

Koleksiyon döndüren API'de asla null döndürülmez — boş koleksiyon döndürülür ki çağıran kontrolsüz foreach yapabilsin; null, her çağırana kontrol yükü dağıtır. Tekil sonuçta karar bilinçlidir: bulunamama olağansa T? döndürülür ve imza/ad bunu söyler (FirstOrDefault kalıbı), olağanüstüyse exception fırlatılır; beklenen başarısızlıkta üçüncü yol TryXxx desenidir (bool + out). Tutarsızlık — aynı API'nin bazen null bazen exception vermesi — en kötü seçenektir.

10Sınav köşesi

Yazılıda nasıl sorulur

(1) Çıktı/sonuç tahmini: int? x = null; Console.WriteLine(x ?? -1); ne yazar (-1); x?.ToString() null iken ne döner (null, exception değil). (2) Exception ayrımı — sevilen tuzak: null üye erişimi NRE, değersiz Nullable.Value InvalidOperationException; şıklarda ikisi karıştırılır. (3) Operatör eşleme: "?? operatörünün işlevi nedir?", "?. zincirinde ara halka null ise ne olur?" (ifade null olur, devamı çalışmaz). (4) Kavram: değer tipleri neden null olamaz (1. dersle birleşik), Nullable<T>'nin struct olduğu, NRT'nin derleme zamanı özelliği olduğu.

Sözlüde nasıl anlatılır — 2 dakikalık taslak

"NullReferenceException'ı nasıl önlersin?" için düzen: (1) Kavram temeli — "null referansın yokluğudur; NRE, yokluk üzerinden üyeye erişince doğar" diye bir cümlede kur. (2) Katmanlı savunma — "önce tasarım: sınırda guard clause ve ThrowIfNull, koleksiyonda boş dönüş, beklenen başarısızlıkta TryParse deseni; sonra dil araçları: ?. ve ?? ile güvenli erişim" sırası, araç saymaktan ayrışan sistemli cevaptır. (3) Modern vurgu: "NRT açık çalışırım — string? sözleşmeyi tipe yazar, derleyici akış analiziyle beni sınırda uyarır; ama bunun çalışma zamanı garantisi olmadığını bilirim, dış girdide doğrulama sürer." (4) Pratik kapanış: "Üretim NRE'sinde stack trace'ten ifadeye, ifadeden null'un kaynağına geriye yürür, düzeltmeyi maskeye değil kaynağa yaparım."