Bölüm 4 / 13
Exception yönetimi
try/catch/finally, özel exception, ne zaman fırlatılır.
9 dakikalık okuma
try/catch yazmayı herkes bilir; bu ders daha zor soruların
dersi: exception fırlayınca makinede gerçekte ne olur, throw; ile
throw ex; arasındaki tek kelimelik fark neden kritik, hangi durumda
fırlatmak yerine TryParse deseni doğru, özel exception ne zaman hak
edilir? Hata yönetimi, kodun mutlu yolundan daha çok kod incelemesi tartışması
üretir — çünkü yanlışı testte değil, gece 03:00 log dosyasında görünür.
01Ne öğreneceksin
- Exception mekanizması: stack unwinding, stack trace okuma
- try/catch/finally: çoklu catch sırası, exception filter (
when), finally garantisi throw;vsthrow ex;ve sarmalama (inner exception) kalıbı- Ne zaman fırlatmalı: BCL exception seçim tablosu, TryParse alternatifi, akış kontrolü yasağı
- Özel exception: hangi durumda gerekir, nasıl yazılır
02Mekanizma: fırlatılan exception makinede ne yapar
Bir hatayı bildirmenin ilkel yolu dönüş koduydu (C dünyası: -1 döndür, çağıran
kontrol etsin) — kontrolü unutan çağıranla hata sessizce yutulurdu. Exception bu
sözleşmeyi tersine çevirir: hata görmezden gelinemez bir nesne olarak fırlar
ve yakalanana kadar programı durdurur. throw anında çalışma zamanı şunu
yapar: bulunduğu metottan başlayarak çağrı yığınını (Hat 02'nin 4. dersindeki
stack çerçeveleri) yukarı doğru söker — buna stack unwinding denir — her
çerçevede uygun bir catch arar; bulursa oradan devam eder, en tepeye
kadar bulamazsa süreç çöker ve o meşhur kırmızı döküm ekrana düşer:
Unhandled exception. System.FormatException: The input string 'abc' was not in a correct format.
at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value)
at System.Int32.Parse(String s)
at Rapor.SatirIsle(String satir) in /app/Rapor.cs:line 42 ← SENİN kodun: buradan başla
at Program.Main() in /app/Program.cs:line 12
Stack trace okuma bir meslek becerisidir: en üst satır fırlatıldığı yer,
aşağısı oraya gelen çağrı zinciri. Kendi kodunun geçtiği ilk satıra git (framework
satırlarını atla), dosya:satır zaten yazıyor. Exception nesnesinin üç temel üyesi:
Message (insan için açıklama), StackTrace (zincir),
InnerException (sarmalanmış kök neden — birazdan). Tüm exception'lar
System.Exception'dan türer — bu kalıtım (Hat 01'in 12. dersi), catch
eşleştirmesinin de temelidir.
03try / catch / finally: sözdiziminin incelikleri
try
{
var icerik = File.ReadAllText(yol);
var deger = int.Parse(icerik);
}
catch (FileNotFoundException ex) // en ÖZELDEN başla
{
Console.WriteLine($"Dosya yok: {ex.FileName}");
}
catch (FormatException) when (ayiklamaModu) // filter: koşul tutmazsa bu catch YOK sayılır
{
Console.WriteLine("Sayı biçimi bozuk (ayıklama modu ayrıntısı).");
}
catch (IOException ex) // daha genel sonra
{
Console.WriteLine($"G/Ç hatası: {ex.Message}");
}
finally
{
// her durumda çalışır: normal çıkış, exception, hatta catch'ten yeniden fırlatma
}
- Catch sırası özelden genele gider; tersini yazarsan derleyici uyarır — genel tip önce gelirse özeli asla yakalanamaz (kalıtım: FileNotFoundException bir IOException'dır).
- Exception filter (
when): koşul sağlanmazsa exception bu catch'e "hiç uğramamış" olur ve aramaya devam eder — yakalayıp yeniden fırlatmaya göre farkı, stack trace'in hiç bozulmaması ve dışarıdaki catch'lerin şansının sürmesidir. Tipik kullanım: belli HTTP koduna, mesaj içeriğine ya da yeniden deneme sayısına göre seçici yakalama. - finally temizlik garantisidir: dosya kapatma, kilit bırakma gibi işler
exception'da da çalışır. Bu kalıbın o kadar yaygın bir özel hâli vardır ki dile
anahtar kelime olmuştur:
using, "finally içinde Dispose çağır" demektir — mekanizmayı 9. ders (cs-idisposable) sökecek.
04throw'un iki hâli ve sarmalama: kanıtı koru
Bir catch bloğunun dört meşru sonu vardır: hatayı çözmek (yeniden dene, varsayılana dön), zenginleştirip yukarı vermek, aynen yukarı vermek ya da — yalnızca en dış katmanda — loglayıp bitirmek. "Yukarı verme"nin yazımı ise mülakat klasiği bir tuzak içerir:
catch (Exception ex)
{
log.Hata(ex);
throw; // DOĞRU: aynı nesne, stack trace KORUNUR
// throw ex; // YANLIŞ: trace bu satırdan yeniden başlar — suç mahalli silindi!
}
throw ex; derlenir, çalışır ve kanıtı yok eder: gece 03:00'te elinde
yalnızca catch satırını gösteren bir trace kalır, hatanın gerçek adresi kaybolmuştur.
Katman değiştirirken ise sarmalama (wrap) doğru araçtır — alt katmanın teknik
hatasını, üst katmanın diliyle yeni bir exception'a koyar ama kökü
InnerException'da saklarsın:
catch (SqlException ex)
{
throw new VeriErisimException($"Müşteri {id} okunamadı.", ex);
// kök neden içeride ↗ — log'da zincir görünür
}
Bir de dördüncü yolun yozlaşmış hâli var, üretim kodunun en tehlikeli üç satırı:
catch (Exception) { } — hata yutma. Program "çalışıyor" görünür,
veri sessizce bozulur, iz yoktur. Kural: ya çöz, ya yukarı ver; loglayıp yutmak
yalnızca en dış sınırın (main döngüsü, request pipeline'ı — Hat 05'te global hata
middleware'i) hakkıdır.
05Ne zaman fırlatmalı: exception bir akış aracı değildir
Ölçüt tek cümledir: exception, istisnai durum içindir — metodun sözleşmesini yerine getirmesini imkânsız kılan durum. Beklenen, olağan başarısızlıklar için akış aracı olarak kullanmak hem tasarım hem performans hatasıdır: fırlatma anı pahalıdır (nesne + stack trace üretimi + unwinding; try bloğunun kendisi ise neredeyse bedava). Kullanıcının her hatalı girişinde exception fırlatan form, saniyede binlerce isabetsiz aramanın olduğu API — bunlar 2. derste tanıdığın TryParse desenine aittir:
// BEKLENEN başarısızlık — akış: Try deseni
if (int.TryParse(girdi, out int yas)) { ... } else { /* kullanıcıya mesaj */ }
// İSTİSNAİ durum — sözleşme ihlali: fırlat
public void Kaydet(Musteri m)
{
ArgumentNullException.ThrowIfNull(m); // çağıran hata yapmış
if (baglanti.Acik == false)
throw new InvalidOperationException("Bağlantı kapalıyken Kaydet çağrılamaz.");
}
Fırlatacağın tipi de uydurmadan önce BCL'nin hazır sözlüğünden seç — çağıranlar bu tipleri tanır:
| Tip | Anlamı |
|---|---|
ArgumentNullException | parametre null gelmemeliydi |
ArgumentOutOfRangeException | parametre aralık dışı (negatif adet gibi) |
ArgumentException | parametre başka biçimde geçersiz |
InvalidOperationException | nesnenin şu anki durumu bu çağrıya uygun değil |
NotSupportedException | bu üye bu tipte hiç desteklenmiyor |
TimeoutException | süre doldu |
Bir yasak daha: Exception, SystemException,
NullReferenceException, StackOverflowException tiplerini
kendin fırlatma — ilk ikisi anlamsızca genel, son ikisi "çalışma zamanının
hata sinyali"dir; senin kodunun NRE fırlatması, hatayı bildirmek değil taklit
etmektir.
06Özel exception: ne zaman hak edilir, nasıl yazılır
Özel exception tipinin tek meşru gerekçesi vardır: çağıranın bu hatayı diğerlerinden ayırt edip farklı davranması gerekiyor. "Bakiye yetersiz"i özel yapan, catch'te ona özel bir yol olmasıdır (kullanıcıya limit öner); kimsenin ayırt etmeyeceği hata için özel tip, sınıf enflasyonudur. Yazımı kısa bir kalıptır:
public class BakiyeYetersizException : Exception
{
public decimal Eksik { get; } // çağıranın karar vermesine yarayan VERİ taşı
public BakiyeYetersizException() { }
public BakiyeYetersizException(string mesaj) : base(mesaj) { }
public BakiyeYetersizException(string mesaj, Exception ic) : base(mesaj, ic) { }
public BakiyeYetersizException(decimal eksik)
: base($"Bakiye {eksik:N2} TL yetersiz.") => Eksik = eksik;
}
Exception'dan türet, adı...Exceptionile bitir.- Üç standart kurucuyu (boş / mesaj / mesaj+inner) koru — sarmalama zinciri ve serileştiriciler buna güvenir.
- Asıl değeri veri taşıyan property'dir: çağıran, mesaj metnini
ayrıştırmak (kırılgan!) yerine
ex.Eksik'i okur. - Alan büyüdükçe taban tip işe yarar:
OdemeExceptiontabanından türeyen aile, çağırana "tüm ödeme hatalarını tek catch'te yakala" imkânı verir — kalıtım hiyerarşisi burada gerçek iş görür.
07Sahada hata yönetimi: katman katman strateji
Olgun bir uygulamada hata yönetimi serpiştirilmiş try'lar değil, katman stratejisidir: iç katmanlar (domain, veri erişimi) hatayı erken fırlatır ve ancak değer katabilecekleri yerde yakalar (sarmalama, yeniden deneme); orta katman beklenen başarısızlıkları Try deseni ya da sonuç nesnesiyle akışa çevirir; en dış sınır — ASP.NET'te global exception middleware'i (Hat 05'te kuracaksın) — yakalanamamış her şeyi loglar ve kullanıcıya güvenli, tutarlı bir hata cevabı üretir. Böylece "her metodda try/catch" gürültüsü yok olur; kodun %95'i hiç exception koduna dokunmaz.
İki saha notu daha: yakaladığını tam nesne olarak logla
(log.Hata(ex) — yalnız ex.Message değil; trace ve inner
zinciri asıl kanıttır). Ve async dünyaya bir köprü: Task içinde biriken
hatalar bazen AggregateException olarak paketlenmiş gelir — mekanizması
8. dersin (cs-async) konusu, adını buradan tanıyor ol.
08Mini alıştırma — Trace'i koru, filtreyi dene
Yaklaşık 20 dakika · dotnet run
- Trace deneyi: üç metotluk zincir yaz (A → B → C; C,
FormatExceptionfırlatsın). B'de yakala ve öncethrow ex;ile, sonrathrow;ile yukarı ver; Main'de yakalayıpex.StackTrace'i yazdır. İki çıktıyı karşılaştır: C hangisinde görünüyor? - Sarmalama: C'nin hatasını B'de
new VeriErisimException("...", ex)ile sarmala; Main'deex.InnerExceptionzincirini while döngüsüyle yazdır. - Filter:
bool ayrintiliModdeğişkeni ekle;catch (FormatException) when (ayrintiliMod)yaz. Modu açıp kapatarak hangi catch'in devreye girdiğini gözle. - Maliyet ölçümü: "1000 hatalı girdiyi işle" senaryosunu iki kez yaz:
önce
int.Parse+ catch ile, sonraint.TryParseile;Stopwatchfarkını not et. Fark nereden geliyor? (Fırlatma anı pahalı, try bloğu değil.) - Özel tip: bölüm 06'daki
BakiyeYetersizException'ı yaz; çekim metodunda fırlat, çağıran taraftaex.Eksik'i kullanarak kullanıcıya öneri mesajı üret.
09Kendini sına
Exception fırlatılınca çalışma zamanı ne yapar? Stack unwinding'i ve trace okumada ilk hamleni anlat.
throw anında çalışma zamanı çağrı yığınını fırlatılan noktadan yukarı doğru söker (stack unwinding), her çerçevede tipe uyan catch arar; bulursa oradan devam eder, en tepeye dek bulamazsa süreç çöker. Trace'te en üst satır fırlatma yeri, altı çağrı zinciridir; okurken framework satırlarını atlayıp kendi kodumun geçtiği ilk dosya:satır'a giderim — kök neden aramasının başlangıç adresi orasıdır.
throw; ile throw ex; farkı nedir? Katman değiştirirken doğru kalıp hangisidir?
throw; aynı exception nesnesini aynen yukarı verir ve stack trace korunur; throw ex; trace'i o satırdan yeniden başlatır — hatanın gerçek adresi kaybolur, bu yüzden loglayıp yukarı verirken daima throw; yazılır. Katman değiştirirken doğru kalıp sarmalamadır: alt katman hatası üst katman diliyle yeni bir exception'a konur ve kök, InnerException parametresiyle taşınır — log'da tüm zincir görünür.
"Exception'ı akış kontrolü için kullanma" kuralının hem tasarım hem performans gerekçesini ver; alternatif desen nedir?
Tasarım gerekçesi: exception, sözleşmenin yerine getirilemediği istisnai durumun sinyalidir; beklenen başarısızlığı (hatalı kullanıcı girdisi, bulunamayan kayıt) exception'la modellemek çağıranı try/catch ile akış yazmaya zorlar. Performans gerekçesi: fırlatma anı pahalıdır — nesne ve stack trace üretimi + unwinding; try bloğunun kendisi ise neredeyse bedavadır. Alternatif, Try desenidir: bool dönüş + out parametre (int.TryParse), beklenen başarısızlığı olağan akışa çevirir.
Özel exception ne zaman gerekir? Yazarken uyulan üç kural nedir?
Tek gerekçe: çağıranın bu hatayı diğerlerinden ayırt edip farklı davranması gerekiyorsa — ayırt eden yoksa BCL'nin hazır tipleri (ArgumentException ailesi, InvalidOperationException) yeter. Kurallar: (1) Exception'dan türet, adı ...Exception ile bitsin; (2) üç standart kurucuyu koru (boş / mesaj / mesaj+inner) — sarmalama zinciri buna dayanır; (3) çağıranın karar vermesine yarayan veriyi mesaj metnine değil property'ye koy (ex.Eksik gibi) — mesaj ayrıştırmak kırılgandır.
10Sınav köşesi
Yazılıda nasıl sorulur
(1) finally klasiği: "catch'te return olsa da finally çalışır mı?" (evet) — çıktı sıralama sorusu olarak gelir (try→catch→finally dizilişi yazdırılır). (2) Catch sırası: özel tip genel tipten önce gelmeli; "hangi catch yakalar?" sorusunda kalıtım bilgisi test edilir (FileNotFoundException bir IOException'dır). (3) throw; / throw ex; farkı — doğrudan ya da "stack trace'i hangisi korur" biçiminde. (4) Kavram eşleme: değersiz Nullable.Value → InvalidOperationException, null üye erişimi → NullReferenceException, hatalı Parse → FormatException; şıklar bu üçünü karıştırtır. (5) "using hangi yapının kısaltmasıdır?" (try/finally + Dispose).
Sözlüde nasıl anlatılır — 2 dakikalık taslak
"Exception stratejin nedir?" için düzen: (1) İlke — "exception istisnai durum içindir; beklenen başarısızlığı Try deseniyle akışa çeviririm, fırlatma anının pahalı olduğunu bilirim." (2) Katman stratejisi — "iç katmanlar erken fırlatır, değer kattığı yerde sarmalar; en dış sınırda global middleware loglar ve tutarlı cevap üretir — her metoda try/catch serpiştirmem" cümlesi, olgun mimari bakışın kanıtıdır. (3) Teknik derinlik — "yukarı verirken throw; kullanırım, throw ex; trace'i siler; katman geçişinde InnerException ile sarmalarım" — tek kelimelik farkı bilmek mülakatta puandır. (4) Kapanış: "Özel exception'ı yalnız çağıran ayırt edecekse yazarım; veriyi mesajda değil property'de taşırım, loglarken tam nesneyi geçerim."
