Langsung ke konten
KamusNgoding
Menengah Csharp 3 menit baca

Exception Handling di C#: Try, Catch, dan Finally

#csharp #exception #try-catch #finally #throw #custom-exception #using #idisposable

Program yang robust bisa menangani kondisi tak terduga dengan elegan — bukan crash atau menampilkan error mentah kepada pengguna. C# menyediakan sistem exception handling yang komprehensif dengan beberapa fitur yang tidak ada di C++: blok finally, when filter untuk catch, dan using statement yang otomatis membersihkan resource melalui interface IDisposable.

Konsep Dasar: try, catch, finally

int[] nilai = { 10, 20, 30 };

try
{
    Console.WriteLine(nilai[5]); // IndexOutOfRangeException!
    Console.WriteLine("Baris ini tidak dieksekusi");
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
    // Output: Error: Index was outside the bounds of the array.
}
finally
{
    // finally SELALU dieksekusi, baik ada exception maupun tidak
    Console.WriteLine("Blok finally selalu jalan");
    // Output: Blok finally selalu jalan
}

Console.WriteLine("Program berlanjut...");

Hierarki Exception C#

System.Exception
├── System.SystemException
│   ├── NullReferenceException    // null reference
│   ├── IndexOutOfRangeException  // indeks array di luar batas
│   ├── InvalidCastException      // konversi tipe gagal
│   ├── StackOverflowException    // rekursi terlalu dalam
│   └── OutOfMemoryException      // memori habis
└── System.ApplicationException   // base untuk custom exception aplikasi
    └── (custom exceptions kamu)

Menangkap Beberapa Exception

static void ProsesInput(string input)
{
    if (string.IsNullOrEmpty(input))
        throw new ArgumentNullException(nameof(input), "Input tidak boleh kosong");

    if (!int.TryParse(input, out int angka))
        throw new FormatException($"'{input}' bukan angka yang valid");

    if (angka < 0)
        throw new ArgumentOutOfRangeException(nameof(angka), "Angka harus positif");

    Console.WriteLine($"Angka valid: {angka}");
}

string[] test_inputs = { null!, "", "abc", "-5", "42" };

foreach (string? input in test_inputs)
{
    try
    {
        ProsesInput(input ?? "");
    }
    catch (ArgumentNullException ex)
    {
        Console.WriteLine($"[Null] {ex.ParamName}: {ex.Message}");
    }
    catch (FormatException ex)
    {
        Console.WriteLine($"[Format] {ex.Message}");
    }
    catch (ArgumentOutOfRangeException ex)
    {
        Console.WriteLine($"[Range] {ex.ParamName}: {ex.Message}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"[Error] {ex.GetType().Name}: {ex.Message}");
    }
}
// Output:
// [Null] input: Input tidak boleh kosong (Parameter 'input')
// [Null] input: Input tidak boleh kosong (Parameter 'input')
// [Format] 'abc' bukan angka yang valid
// [Range] angka: Angka harus positif (Parameter 'angka')
// Angka valid: 42

throw vs throw ex — Perbedaan Penting

// ❌ throw ex — menghapus stack trace asli!
try { /* ... */ }
catch (Exception ex)
{
    // Log error
    throw ex; // Stack trace dimulai dari sini, bukan dari sumber asli
}

// ✅ throw — mempertahankan stack trace asli
try { /* ... */ }
catch (Exception ex)
{
    // Log error
    throw; // Re-throw exception yang sama dengan stack trace asli
}

// ✅ Wrap exception — tambahkan konteks
try { /* ... */ }
catch (IOException ex)
{
    throw new ApplicationException("Gagal membaca file konfigurasi", ex);
    // InnerException menyimpan exception asli
}

when — Filter Catch

int percobaan = 0;

for (int i = 0; i < 5; i++)
{
    try
    {
        percobaan++;
        if (percobaan < 3) throw new TimeoutException("Koneksi timeout");
        Console.WriteLine($"Berhasil pada percobaan ke-{percobaan}");
        break;
    }
    catch (TimeoutException ex) when (percobaan < 3) // Filter: hanya tangkap jika < 3x
    {
        Console.WriteLine($"Percobaan {percobaan} gagal: {ex.Message}. Coba lagi...");
    }
    catch (TimeoutException ex) // Tangkap sisa (percobaan >= 3, tapi tidak sampai sini)
    {
        Console.WriteLine($"Menyerah setelah {percobaan} percobaan.");
        break;
    }
}
// Output:
// Percobaan 1 gagal: Koneksi timeout. Coba lagi...
// Percobaan 2 gagal: Koneksi timeout. Coba lagi...
// Berhasil pada percobaan ke-3

using Statement — Resource Management Otomatis

using memastikan Dispose() dipanggil otomatis — pengganti manual try/finally:

// Tanpa using — verbose
StreamReader? reader = null;
try
{
    reader = new StreamReader("data.txt");
    string konten = reader.ReadToEnd();
    Console.WriteLine(konten);
}
finally
{
    reader?.Dispose(); // Dispose dipanggil manual
}

// Dengan using — otomatis Dispose saat keluar blok
using (var reader = new StreamReader("data.txt"))
{
    string konten = reader.ReadToEnd();
    Console.WriteLine(konten);
} // reader.Dispose() dipanggil otomatis

// using declaration (C# 8+) — lebih ringkas
using var reader2 = new StreamReader("data.txt");
string isi = reader2.ReadToEnd();
Console.WriteLine(isi);
// Dispose dipanggil saat variabel keluar scope

Custom Exception

// Exception khusus untuk domain aplikasi
public class ValidationException : ApplicationException
{
    public string FieldName { get; }
    public object? InvalidValue { get; }

    public ValidationException(string fieldName, object? value, string message)
        : base(message)
    {
        FieldName = fieldName;
        InvalidValue = value;
    }
}

public class UnauthorizedException : ApplicationException
{
    public string RequiredRole { get; }

    public UnauthorizedException(string role)
        : base($"Aksi ini membutuhkan role: {role}")
    {
        RequiredRole = role;
    }
}

// Penggunaan
static void UpdateProduk(string nama, decimal harga, string userRole)
{
    if (userRole != "admin")
        throw new UnauthorizedException("admin");

    if (string.IsNullOrWhiteSpace(nama))
        throw new ValidationException("nama", nama, "Nama produk tidak boleh kosong");

    if (harga < 0)
        throw new ValidationException("harga", harga, "Harga tidak boleh negatif");

    Console.WriteLine($"Produk '{nama}' diperbarui: Rp {harga:N0}");
}

try
{
    UpdateProduk("Laptop", 15_000_000m, "user");
}
catch (UnauthorizedException ex)
{
    Console.WriteLine($"403 Forbidden: {ex.Message}");
    // Output: 403 Forbidden: Aksi ini membutuhkan role: admin
}
catch (ValidationException ex)
{
    Console.WriteLine($"422 Unprocessable: Field '{ex.FieldName}' — {ex.Message}");
}

Pertanyaan yang Sering Diajukan

Apa perbedaan throw dan throw ex di C#?

throw (tanpa argumen) melempar ulang exception yang sedang ditangkap dan mempertahankan stack trace asli — kamu bisa lihat di mana exception pertama kali terjadi. throw ex melempar exception yang sama tapi menghapus stack trace asli dan menggantikannya dengan lokasi saat ini. Hampir selalu gunakan throw, bukan throw ex.

Kapan menggunakan finally vs using?

Gunakan using untuk objek yang mengimplementasikan IDisposable (file, koneksi database, HTTP client) — lebih ringkas dan idiomatik. Gunakan finally untuk cleanup yang tidak melibatkan IDisposable, seperti reset flag, tutup koneksi yang tidak punya Dispose, atau logging yang selalu harus terjadi.

Apakah exception handling berdampak pada performa?

Blok try sendiri hampir tidak ada overhead. Overhead terjadi saat exception benar-benar dilempar — pembuatan stack trace bisa lambat. Karena itu, jangan gunakan exception untuk flow control normal. Contoh: gunakan int.TryParse() yang mengembalikan bool daripada menangkap FormatException untuk input yang sering tidak valid.

Apa itu InnerException dan kapan menggunakannya?

InnerException menyimpan exception asli yang menjadi penyebab exception baru. Berguna saat kamu ingin menambahkan konteks ke exception tanpa kehilangan informasi asli: throw new AppException("Gagal simpan data", ex) — di sini ex adalah InnerException. Saat debugging, kamu bisa telusuri rantai exception hingga ke penyebab aslinya.

Kesimpulan

KonsepSintaksKeterangan
try/catchtry { } catch (Ex e) { }Tangkap exception tertentu
finallyfinally { }Selalu dieksekusi
Re-throw amanthrow;Pertahankan stack trace
when filtercatch (Ex e) when (kondisi)Filter tambahan saat catch
usingusing var x = new X()Auto-dispose resource
Custom exceptionclass MyEx : ExceptionException domain spesifik

Artikel sebelumnya: Inheritance dan Polymorphism di C# — hierarki class di C#.

Langkah selanjutnya: File I/O di C# — cara membaca dan menulis file di C#.

Artikel Terkait