Program yang benar saat ditulis tetap dapat menemui keadaan yang tidak dapat dilanjutkan, misalnya pembagian dengan nol atau teks yang gagal diuraikan menjadi angka. Untuk keadaan semacam itu, .NET menyediakan exception, yaitu objek yang dibuat dan dilempar (throw) ketika error terjadi, kemudian bergerak melintasi rantai pemanggilan metode sampai ditemukan penangan yang cocok. Topik ini membahas struktur try, catch, dan finally, urutan blok catch dari yang spesifik ke yang umum, serta filter, yaitu kondisi tambahan pada sebuah blok catch. Setelah topik ini dipahami, kode yang berisiko gagal dapat dibungkus penanganan yang tepat, dan pembersihan akhir tetap dijamin berjalan.
Alur exception#
Exception adalah objek dari kelas yang menurunkan System.Exception, dan setiap error saat program berjalan dilaporkan melalui objek semacam itu. Ketika sebuah exception dilempar, runtime, yaitu lapisan yang menjalankan program C#, mencari blok catch yang cocok pada metode yang sedang berjalan. Apabila metode tersebut tidak memilikinya, pencarian berpindah ke metode pemanggilnya, dan seterusnya sampai ujung call stack, yakni rantai metode yang sedang bersarang saat program berjalan. Exception yang tidak pernah tertangkap mencetak laporan error lalu menghentikan program.
Struktur try dan catch membungkus kode yang berpotensi melempar exception. Baris di dalam blok try dijalankan sesuai urutan normal, tetapi begitu sebuah exception dilempar, sisa pernyataan pada blok tersebut diloncati dan eksekusi berpindah ke blok catch yang tipenya cocok. Program berikut menampilkan kedua jalur tersebut untuk tiga pembagi yang berbeda. Salin programnya ke berkas Program.cs, lalu jalankan dengan dotnet run --file Program.cs.
// Program dasar try/catch/finally: alur normal, exception tertangkap, dan finally.
int[] daftarPembagi = { 2, 0, 4 };
foreach (int pembagi in daftarPembagi)
{
try
{
Console.WriteLine($"Hasil 10 / {pembagi} = {10 / pembagi}");
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Exception tertangkap: {ex.Message}");
}
finally
{
Console.WriteLine($"Percobaan dengan pembagi {pembagi} selesai.");
}
}Hasil 10 / 2 = 5
Percobaan dengan pembagi 2 selesai.
Exception tertangkap: Attempted to divide by zero.
Percobaan dengan pembagi 0 selesai.
Hasil 10 / 4 = 2
Percobaan dengan pembagi 4 selesai.Pembagi 2 dan 4 mengikuti jalur normal, yaitu pernyataan di dalam try dijalankan penuh lalu finally menyusul. Pembagi 0 memicu DivideByZeroException pada pernyataan pembagian, sehingga pernyataan yang menampilkan hasil tidak pernah dijalankan dan eksekusi langsung berpindah ke blok catch. Blok finally tetap dieksekusi pada ketiga percobaan, dan urutan keluarannya memperlihatkan hal itu.
Exception tidak wajib ditangani di metode tempat ia muncul. Bila metode yang sedang berjalan tidak menyediakan blok catch yang cocok, exception naik ke pemanggilnya beserta seluruh isinya. Program berikut menangkap exception yang dilempar di dalam metode lain.
// Exception yang tidak ditangani di dalam metode bergerak naik ke pemanggilnya.
static int KaliSeratus(int pembagi) => 100 / pembagi;
try
{
Console.WriteLine(KaliSeratus(0));
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Tertangkap di pemanggil: {ex.Message}");
}Tertangkap di pemanggil: Attempted to divide by zero.Beberapa blok catch#
Satu blok try dapat diikuti beberapa blok catch, masing-masing untuk satu jenis exception. Blok-blok tersebut diperiksa dari atas ke bawah, dan blok pertama yang jenisnya cocok dengan exception yang dilempar itulah yang dijalankan. Blok paling umum adalah catch (Exception), karena semua exception menurunkan kelas tersebut. Karena pencocokannya berurutan, blok yang spesifik wajib ditulis lebih dahulu, sedangkan blok umum ditempatkan terakhir sebagai penampung sisanya.
// Catch spesifik ditulis lebih dahulu, catch umum menampung sisanya.
foreach (string teks in new[] { "12", "ab" })
{
try
{
Console.WriteLine($"Angka: {int.Parse(teks)}");
}
catch (FormatException ex)
{
Console.WriteLine($"Format salah: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Exception lain: {ex.Message}");
}
}Angka: 12
Format salah: The input string 'ab' was not in a correct format.Teks ab memicu FormatException, sehingga blok pertama menangkapnya dan pesan aslinya dicetak. Bila jenis exceptionnya bukan FormatException, misalnya OverflowException, blok pertama tidak cocok dan penanganan jatuh ke catch (Exception). Blok umum semacam itu sebaiknya hanya mencatat kejadian, lalu melempar ulang atau menghentikan program, karena tindakan spesifik tidak mungkin ditentukan di sana.
finally#
Blok finally menampung pernyataan yang wajib dijalankan apa pun yang terjadi pada blok try, baik berakhir normal, berakhir dengan exception tertangkap, maupun berakhir dengan return. Runtime menjamin blok ini dieksekusi sebelum alur program berlanjut atau sebelum exception naik ke pemanggil. Karena jaminan itu, finally adalah tempat pembersihan sumber daya seperti penutupan berkas atau koneksi jaringan.
// finally tetap dijalankan meskipun try berakhir dengan return.
static int Contoh()
{
try
{
Console.WriteLine("Blok try selesai dengan return.");
return 7;
}
finally
{
Console.WriteLine("Blok finally tetap berjalan.");
}
}
Console.WriteLine($"Nilai kembali: {Contoh()}");Blok try selesai dengan return.
Blok finally tetap berjalan.
Nilai kembali: 7Blok try pada program di atas berakhir dengan return 7, tetapi baris pada finally tetap tercetak sebelum nilai kembali diterima pemanggil. Urutan keluarannya menegaskan bahwa finally bukan bagian dari blok try, melainkan langkah penutup yang dijalankan runtime setelah eksekusi blok try selesai. Pola try dan finally inilah yang menjadi dasar pernyataan using dalam pembersihan sumber daya.
Exception filter dengan when#
Blok catch dapat dipasangi filter, yaitu ekspresi boolean yang ditulis dengan kata kunci when setelah tipe exception. Filter mempersempit satu jenis exception menjadi beberapa penangan sesuai kondisinya, tanpa perlu memeriksa properti di dalam blok lalu melempar ulang. Blok catch dengan filter hanya cocok apabila tipenya cocok dan ekspresi filternya bernilai true. Bila filter bernilai false, pencarian blok catch berlanjut ke blok berikutnya seolah filter itu tidak ada.
// Exception filter mempersempit catch tanpa menambah jenis blok baru.
foreach (int nilai in new[] { -5, 150 })
{
try
{
Periksa(nilai);
Console.WriteLine($"Nilai {nilai} diterima.");
}
catch (ArgumentOutOfRangeException ex) when (ex.ParamName == "nilai")
{
Console.WriteLine($"Filter pertama: {ex.Message}");
}
catch (ArgumentOutOfRangeException ex)
{
Console.WriteLine($"Filter kedua: {ex.Message}");
}
}
static void Periksa(int nilai)
{
if (nilai < 0)
{
throw new ArgumentOutOfRangeException(nameof(nilai), "Nilai tidak boleh negatif.");
}
if (nilai > 100)
{
throw new ArgumentOutOfRangeException("batas", "Nilai melebihi 100.");
}
}Filter pertama: Nilai tidak boleh negatif. (Parameter 'nilai')
Filter kedua: Nilai melebihi 100. (Parameter 'batas')Nilai -5 ditolak dengan parameter bernama nilai, sehingga filter pertama yang memeriksa ex.ParamName cocok dan menangkapnya. Nilai 150 juga melempar ArgumentOutOfRangeException, tetapi nama parameternya adalah batas, sehingga filter pertama bernilai false dan penanganan jatuh ke blok kedua. Perilaku ini berguna ketika satu jenis exception membawa beberapa keadaan yang penanganannya berbeda.
Kesalahan umum#
- Blok catch umum ditempatkan sebelum blok spesifik. Kompilator menolaknya dengan error CS0160 karena blok terakhir menjadi tidak dapat dicapai, sebagaimana contoh berikut. Perbaikannya adalah menukar urutannya sehingga blok spesifik berada di atas.
- Pembersihan ditulis di akhir blok
tryalih-alih difinally. Baris setelah pernyataan yang melempar exception tidak pernah dijalankan, sehingga sumber daya tidak tertutup. Pindahkan pembersihannya kefinally, atau gunakan pernyataanusing. catch (Exception)dipakai untuk semua keadaan. Semua jenis error tampak tertangani, tetapi bug pemrograman sepertiNullReferenceExceptionikut disembunyikan dan program berlanjut dalam keadaan rusak. Tangkap jenis yang maknanya diketahui, dan biarkan sisanya naik.
// tidak-dijalankan
// Kompilator menolak urutan ini dengan error CS0160, karena catch (Exception)
// di atas sudah menangkap semua exception, termasuk FormatException.
try
{
int.Parse("12");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
}Ringkasan#
- Exception bergerak melintasi rantai pemanggilan sampai menemukan blok catch yang cocok, dan program berhenti bila tidak ada yang menangkapnya.
- Beberapa blok catch diperiksa dari atas ke bawah, sehingga blok spesifik ditulis sebelum blok umum seperti
catch (Exception). - Blok
finallydijamin dijalankan pada semua akhir bloktry, sehingga menjadi tempat pembersihan sumber daya. - Filter
whenmenambahkan kondisi pada blok catch, dan bila filternya false, pencarian berlanjut ke blok berikutnya.
Referensi#
- Exception-handling statements - C# reference — Microsoft Learn — Aturan formal pernyataan throw, try, catch, dan finally beserta filter exception pada C#.
- when contextual keyword - C# reference — Microsoft Learn
— Sintaks filter exception dengan
whendan contoh pemakaiannya. - Exceptions and exception handling - C# guide — Microsoft Learn — Gambaran penanganan exception di C#, mulai dari alurnya hingga jenis-jenis bawaan.