Yükleniyor...

C# Hata Denetimi (try, catch, finally)

C#’ta try, catch ve finally bloklarını kullanarak hataları yakalamayı ve güvenli hata yönetimi yapmayı örneklerle öğrenin.

C#’ta hata denetimi, program çalışırken oluşabilecek beklenmedik durumları yakalayıp kontrol altına almak için kullanılır. Bu sayede program aniden çökmez, kullanıcıya uygun mesaj verilebilir veya alternatif bir yol izlenebilir. Hata yönetiminde try, catch ve finally blokları birlikte kullanılır.


try-catch Kullanımı

try bloğu içinde hataya yol açabilecek kodlar yazılır. Eğer bir hata (exception) oluşursa catch bloğu devreye girer.


try
{
    int sayi = int.Parse("abc"); // Geçersiz dönüşüm
    Console.WriteLine("Sayı: " + sayi);
}
catch (FormatException ex)
{
    Console.WriteLine("Hata: Geçersiz sayı formatı.");
}
// Çıktı:
Hata: Geçersiz sayı formatı.

Birden Fazla catch

Farklı hata türleri için birden fazla catch kullanılabilir. Bu sayede her hataya özel çözüm uygulanabilir.


try
{
    int[] sayilar = { 1, 2, 3 };
    Console.WriteLine(sayilar[5]); // Dizi taşması
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Hata: Dizi sınırları aşıldı.");
}
catch (Exception ex)
{
    Console.WriteLine("Beklenmeyen hata: " + ex.Message);
}

İleri Seviye Hata Yönetimi: Exception Filters (when)

C# dilinde exception filter (istisna filtresi), catch bloğuna when anahtar kelimesi ile bir koşul eklemenizi sağlar. Böylece bir istisna (exception) yalnızca belirli bir koşul sağlandığında yakalanabilir.

Exception filter’ın en önemli avantajı, koşulun catch bloğuna girilmeden önce değerlendirilmesidir. Eğer koşul false dönerse, runtime o catch bloğunu atlar ve uygun başka bir catch bloğu aramaya devam eder.

Bu yaklaşım sayesinde:

Örnek: HTTP Hatalarını Status Code’a Göre Yönetmek

Gerçek dünya uygulamalarında HTTP istekleri farklı durum kodları (status code) döndürebilir. Exception filter kullanarak belirli durum kodlarını ayrı ayrı ele alabilir, kodun yapısını daha düzenli ve okunabilir tutabiliriz.


using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        using var client = new HttpClient();

        try
        {
            HttpResponseMessage response =
                await client.GetAsync("https://api.example.com/users/1");

            response.EnsureSuccessStatusCode();

            string content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
        }
        catch (HttpRequestException ex) 
            when (ex.StatusCode == HttpStatusCode.NotFound)
        {
            Console.WriteLine("Kaynak bulunamadı (404).");
        }
        catch (HttpRequestException ex) 
            when (ex.StatusCode == HttpStatusCode.Unauthorized)
        {
            Console.WriteLine("Yetkisiz erişim (401). Kimlik bilgilerinizi kontrol edin.");
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine("Genel bir HTTP hatası oluştu.");
            Console.WriteLine($"Durum Kodu: {ex.StatusCode}");
        }
    }
}
  

Bu örnekte:

  • when koşulu, catch bloğuna girilmeden önce değerlendirilir.
  • Koşul false ise runtime ilgili catch bloğunu atlar.
  • Exception filter özelliği C# 6 ile birlikte gelmiştir ve modern backend geliştirmede yaygın olarak kullanılır.

Rethrowing Exceptions: throw; vs throw ex;

Bir catch bloğu içinde yakalanan bir exception tekrar fırlatılmak istenebilir. Ancak throw; ve throw ex; aynı davranışı göstermez.

Aralarındaki temel fark, stack trace bilgisinin korunup korunmamasıdır. Stack trace, hatanın hangi metot zinciri üzerinden geldiğini ve tam olarak hangi satırda oluştuğunu gösterir.

Örnek


using System;

class Program
{
    static void Main()
    {
        try
        {
            MethodA();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Hata loglandı.");

            // Doğru kullanım:
            throw;

            // Yanlış kullanım:
            // throw ex;
        }
    }

    static void MethodA()
    {
        MethodB();
    }

    static void MethodB()
    {
        throw new InvalidOperationException("Bir hata oluştu.");
    }
}
  

Bu örnekte throw; kullanılırsa hata, MethodB içinde başladığı şekilde stack trace içerisinde görünür.

Eğer throw ex; kullanılırsa, hata catch bloğundan başlamış gibi görünür ve orijinal çağrı zinciri kaybolur.

  • Exception yeniden fırlatılacaksa her zaman throw; tercih edilmelidir.
  • throw ex; hata ayıklamayı zorlaştırır çünkü stack trace bilgisini bozar.
  • throw anahtar kelimesi ile custom exception sınıfları da fırlatılabilir.

Custom Exceptions (Özel Exception Sınıfları)

Gerçek dünya uygulamalarında her hata Exception ya da diğer genel exception türleri ile ifade edilmemelidir. İş kurallarına (business logic) özel durumları daha anlamlı ve yönetilebilir hale getirmek için custom exception sınıfları oluşturulabilir.

Bu yaklaşım:

Örnek


// 1. Custom exception tanımları
public class ProjectNotFoundException : Exception
{
    public ProjectNotFoundException(string message) : base(message) { }
}

public class ProjectIsCanceledException : Exception
{
    public int ProjectId { get; }
    public string ProjectName { get; }

    public ProjectIsCanceledException(int projectId, string projectName)
        : base($"Project '{projectName}' (Id: {projectId}) is canceled and cannot be processed.")
    {
        ProjectId = projectId;
        ProjectName = projectName;
    }
}

// 2. Kullanımı
public void ProcessProject(Project project)
{
    if (project == null)
    {
        throw new ProjectNotFoundException("Proje sistemde kayıtlı değil.");
    }

    if (project.Status == ProjectStatus.Canceled)
    {
        throw new ProjectIsCanceledException(project.Id, project.Name);
    }

    // ...
}
  

Bu örnekte sistemde bulunmayan bir proje için ProjectNotFoundException fırlatılır.

Eğer proje iptal edilmiş durumdaysa, ProjectIsCanceledException yalnızca bir hata mesajı değil, aynı zamanda ProjectId ve ProjectName bilgilerini de taşır. Bu sayede loglama ve hata analizi daha anlamlı hale gelir.

  • Custom exception sınıfları genellikle iş kurallarına özel durumları temsil eder.
  • İsimlendirme genellikle SomethingException formatında yapılır.
  • Ek propertyler eklenerek hata hakkında daha fazla bağlamsal bilgi taşınabilir.

finally Bloğu

finally bloğu, hata olsun veya olmasın her zaman çalıştırılır. Genellikle dosya kapatma, veritabanı bağlantısı sonlandırma gibi temizleme işlemleri için kullanılır.


try
{
    Console.WriteLine("Dosya açılıyor...");
    throw new Exception("Dosya bulunamadı!");
}
catch (Exception ex)
{
    Console.WriteLine("Hata: " + ex.Message);
}
finally
{
    Console.WriteLine("Dosya kapatılıyor...");
}
// Çıktı:
Dosya açılıyor...
Hata: Dosya bulunamadı!
Dosya kapatılıyor...

Özel Exception Fırlatma

Kendi hata koşullarınızı tanımlayıp throw ile hata fırlatabilirsiniz.


static void Bolme(int a, int b)
{
    if (b == 0)
        throw new DivideByZeroException("Sıfıra bölme hatası!");
    Console.WriteLine("Sonuç: " + (a / b));
}

static void Main()
{
    try
    {
        Bolme(10, 0);
    }
    catch (DivideByZeroException ex)
    {
        Console.WriteLine("Hata: " + ex.Message);
    }
}
// Çıktı:
Hata: Sıfıra bölme hatası!

TL;DR

  • try: Exception fırlatabilecek kod bloğunu içerir.
  • catch: Exception oluştuğunda hatayı yakalar ve işler.
  • finally: Her zaman çalışır, genellikle temizlik işlemleri için kullanılır.
  • Birden fazla catch bloğu, farklı exception türlerini ayrı ayrı ele almayı sağlar.
  • Exception filters (when) koşullu exception yakalamaya imkan verir.
  • throw; exception yeniden fırlatılırken orijinal stack trace bilgisini korur.
  • throw ex; stack trace bilgisini sıfırlar ve genellikle kaçınılmalıdır.
  • Custom exception sınıfları, domain veya iş kuralı hatalarını temsil etmek için kullanılır.


İlişkili Makaleler

C# ile SOLID Prensipleri

C# örnekleriyle SOLID prensiplerinin uygulanışı: daha esnek, sürdürülebilir ve test edilebilir kod tasarımları.