Exception bawaan .NET mencakup kegagalan umum seperti pembagian nol dan referensi null, tetapi kegagalan yang khas pada ranah aplikasi kadang memerlukan jenis yang lebih bermakna. Topik ini membahas standar pelemparan exception yang baik, yaitu memilih jenis bawaan yang sesuai penyebabnya, lalu membahas pembuatan kelas exception kustom ketika data tambahan perlu dibawa bersama error. Bagian terakhir menjelaskan inner exception, yakni exception asli yang disimpan sebagai penyebab dari exception baru. Setelah topik ini, exception yang dilempar program dapat menceritakan kegagalannya secara jelas dan dapat diperiksa oleh pemanggilnya.
Standar pelemparan exception#
Melempar exception dimulai dengan memilih jenis yang paling menggambarkan penyebabnya, bukan sekadar melempar Exception. Untuk masukan metode yang tidak sah, .NET menyediakan ArgumentException beserta turunannya, yaitu ArgumentNullException untuk masukan null dan ArgumentOutOfRangeException untuk nilai di luar rentang. Untuk keadaan objek yang membuat operasi tidak mungkin dijalankan, jenisnya adalah InvalidOperationException. Argumen nameof pada konstruktor menyebut nama parameter secara aman, karena nama itu ikut berubah otomatis apabila parameter diganti namanya.
Metode berikut menolak array null dan array kosong dengan jenis yang berbeda, lalu pemanggilnya menangkap keduanya dengan satu blok catch. Salin programnya ke berkas Program.cs, lalu jalankan dengan dotnet run --file Program.cs.
// Penolakan masukan memakai exception bawaan sesuai penyebabnya.
static double RataRata(int[] nilai)
{
if (nilai is null)
{
throw new ArgumentNullException(nameof(nilai));
}
if (nilai.Length == 0)
{
throw new ArgumentException("Array nilai tidak boleh kosong.", nameof(nilai));
}
int total = 0;
foreach (int n in nilai)
{
total += n;
}
return (double)total / nilai.Length;
}
foreach (int[] masukan in new[] { null!, new int[0], new[] { 70, 80, 90 } })
{
try
{
Console.WriteLine($"Rata-rata: {RataRata(masukan)}");
}
catch (ArgumentException ex)
{
Console.WriteLine($"Ditolak: {ex.Message}");
}
}Ditolak: Value cannot be null. (Parameter 'nilai')
Ditolak: Array nilai tidak boleh kosong. (Parameter 'nilai')
Rata-rata: 80ArgumentNullException menurunkan ArgumentException, sehingga satu blok catch (ArgumentException) menangkap kedua jenis tersebut. Pesan yang dicetak menyertakan nama parameter secara otomatis, dan pesan bawaan untuk kasus null dibuat oleh .NET tanpa perlu ditulis manual. Aturan dasarnya, pesan pelemparan menyatakan apa yang salah dan nilai apa yang bermasalah, sedangkan keputusan penanganannya diserahkan kepada pemanggil.
Membuat exception kustom#
Exception kustom dibuat dengan menurunkan kelas dari Exception atau dari turunannya yang relevan. Konvensi penamaannya adalah nama kelas berakhiran Exception, misalnya NilaiTidakValidException, sehingga jenisnya mudah dikenali dari namanya. Kelas semacam ini berguna ketika pemanggil perlu membaca data tambahan yang tidak dimiliki exception bawaan, misalnya nilai yang ditolak atau kode kegagalannya. Properti tambahan dinyatakan sebagai property, yakni anggota kelas yang dibaca dan ditulis melalui aksesor, dan badan konstruktor meneruskannya ke konstruktor kelas dasar bersama pesannya.
Tetapkan kelasnya sebagai public apabila exception dilempar dari library, yaitu kumpulan kode yang dipakai ulang oleh aplikasi lain, karena pemanggil tidak dapat menangkap jenis yang tidak terlihat. Kelas yang hanya bergerak di dalam satu aplikasi cukup internal. Letakkan kelasnya pada berkas tersendiri atau di dekat tipe yang melemparnya, sehingga penelusuran jenisnya mudah diikuti.
Program berikut memvalidasi nilai ujian pada rentang 0 sampai 100. Kegagalan penguraian teks dibungkus menjadi exception kustom bersama penyebabnya, sedangkan nilai di luar rentang dilempar langsung dengan properti Nilai yang terisi. Salin programnya ke berkas Program.cs, lalu jalankan dengan dotnet run --file Program.cs.
// Program dasar exception kustom: pelemparan, properti tambahan, dan inner exception.
foreach (string teks in new[] { "85", "150", "delapan" })
{
try
{
int nilai = BacaNilai(teks);
Console.WriteLine($"Nilai sah: {nilai}");
}
catch (NilaiTidakValidException ex)
{
Console.WriteLine($"Tipe: {ex.GetType().Name}");
Console.WriteLine($"Pesan: {ex.Message}");
Console.WriteLine($"Nilai tertolak: {ex.Nilai}");
Console.WriteLine($"Penyebab langsung: {ex.InnerException?.GetType().Name ?? "tidak ada"}");
}
}
static int BacaNilai(string teks)
{
try
{
int nilai = int.Parse(teks);
if (nilai < 0 || nilai > 100)
{
throw new NilaiTidakValidException($"Nilai {nilai} berada di luar rentang 0 sampai 100.", nilai);
}
return nilai;
}
catch (FormatException ex)
{
throw new NilaiTidakValidException($"Teks '{teks}' bukan angka bulat.", -1, ex);
}
}
// Exception kustom menurunkan Exception, berakhiran Exception, dan membawa data tambahan.
class NilaiTidakValidException : Exception
{
public int Nilai { get; }
public NilaiTidakValidException(string pesan, int nilai) : base(pesan)
{
Nilai = nilai;
}
public NilaiTidakValidException(string pesan, int nilai, Exception inner) : base(pesan, inner)
{
Nilai = nilai;
}
}Nilai sah: 85
Tipe: NilaiTidakValidException
Pesan: Nilai 150 berada di luar rentang 0 sampai 100.
Nilai tertolak: 150
Penyebab langsung: tidak ada
Tipe: NilaiTidakValidException
Pesan: Teks 'delapan' bukan angka bulat.
Nilai tertolak: -1
Penyebab langsung: FormatExceptionNilai 85 lolos validasi tanpa exception. Nilai 150 ditolak dengan pesan yang menyatakan rentangnya, dan properti Nilai membawa angka yang ditolak sehingga pemanggil dapat menampilkannya kembali. Teks delapan memicu FormatException di dalam metode, dan FormatException itu dibungkus menjadi NilaiTidakValidException melalui konstruktor yang menerima parameter penyebab, sehingga properti InnerException mengarah ke FormatException.
Konstruktor standar dan inner exception#
Kelas exception kustom umumnya menyediakan tiga konstruktor standar, yaitu konstruktor tanpa argumen, konstruktor dengan pesan, dan konstruktor dengan pesan beserta parameter Exception untuk inner exception. Ketiganya membuat kelas kompatibel dengan kebiasaan seluruh exception bawaan .NET, sehingga pemanggil dapat memilih bentuk yang paling sesuai dengan konteksnya.
// Konstruktor standar exception kustom beserta pemakaiannya.
var tanpaPesan = new GagalHitungException();
var denganPesan = new GagalHitungException("Pembagian dengan nol ditolak.");
var denganPenyebab = new GagalHitungException("Perhitungan gagal.", new DivideByZeroException());
Console.WriteLine($"1: {tanpaPesan.Message}");
Console.WriteLine($"2: {denganPesan.Message}");
Console.WriteLine($"3: {denganPenyebab.Message} Penyebab: {denganPenyebab.InnerException?.Message}");
class GagalHitungException : Exception
{
public GagalHitungException() { }
public GagalHitungException(string pesan) : base(pesan) { }
public GagalHitungException(string pesan, Exception inner) : base(pesan, inner) { }
}1: Exception of type 'GagalHitungException' was thrown.
2: Pembagian dengan nol ditolak.
3: Perhitungan gagal. Penyebab: Attempted to divide by zero.Baris ketiga keluaran memperlihatkan mekanisme inner exception dengan jelas, yaitu pesan baru tetap utuh sementara pesan penyebabnya tetap dapat dibaca melalui InnerException. Membungkus exception diperlukan ketika lapisan aplikasi tidak ingin membocorkan detail teknis ke luar, tetapi tetap menyimpan jejak penyebabnya untuk diagnosis. Jejak exception asli tidak hilang, karena objek aslinya tetap hidup di dalam properti tersebut.
Rantai pembungkusan boleh terjadi pada lebih dari satu lapis, dan metode GetBaseException pada Exception menelusurinya sampai penyebab pertama. Bila rantainya perlu dibaca satu per satu, telusuri InnerException dengan perulangan while hingga bernilai null. Lapisan terdalam itulah yang biasanya paling berguna saat penyebab teknisnya dicari.
Kesalahan umum#
- Melempar
Exceptiongenerik. Pemanggil terpaksa menangkap semua jenis atau menganalisis pesan sebagai teks biasa. Gunakan jenis bawaan yang sesuai, atau buat exception kustom bila tidak ada yang cocok. - Inner exception dibuang saat membungkus. Pernyataan
throw new NilaiTidakValidException("pesan")di dalam blok catch yang tidak menyertakanexmemutus jejak penyebabnya. Selalu lewatkan exception yang sedang ditangkap pada konstruktor yang menerima parameterException. - Exception kustom dibuat tanpa data tambahan. Bila tidak ada properti yang perlu dibawa, exception bawaan sudah cukup, dan kelas baru hanya menambah kerumitan tanpa manfaat.
Ringkasan#
- Pilih exception bawaan yang sesuai penyebabnya, seperti
ArgumentExceptionuntuk masukan tidak sah danInvalidOperationExceptionuntuk keadaan objek yang salah. - Exception kustom menurunkan
Exception, berakhiranException, dan membawa properti tambahan yang berguna bagi pemanggilnya. - Tiga konstruktor standar, yaitu tanpa argumen, dengan pesan, dan dengan pesan beserta inner exception, menjadikan kelas tersebut kompatibel dengan kebiasaan .NET.
InnerExceptionmenyimpan exception asli saat sebuah exception dibungkus, sehingga jejak penyebabnya tetap dapat ditelusuri.
Referensi#
- Creating and Throwing Exceptions - C# — Microsoft Learn — Panduan membuat exception kustom, standar konstruktor, dan penyusunan pesan pelemparan.
- Best practices for exceptions - .NET — Microsoft Learn — Aturan resmi pelemparan dan penanganan exception pada .NET.
- Exceptions and exception handling - C# guide — Microsoft Learn — Gambaran penanganan exception di C# beserta jenis-jenis bawaannya.