Saat ada yang salah ketika program berjalan (file tidak ada, teks bukan angka, key tidak ada di dictionary), .NET melempar exception: sebuah objek yang menjelaskan error tersebut. Exception naik lewat call stack sampai ada blok catch yang menanganinya. Kalau tidak ada, program berhenti dan mencetak error-nya.
try catch dasar
Bungkus kode yang bisa gagal dengan try, dan tangani kegagalannya di catch:
Output:
42 doubled is 84
'forty-two' is not a number
7 doubled is 14
Still running
Saat int.Parse("forty-two") melempar exception, Console.WriteLine setelahnya di blok try dilewati, blok catch (FormatException) dijalankan, dan loop berlanjut. Kamu boleh tidak menulis variabel (catch (FormatException)) kalau tidak membutuhkan objek exception-nya.
Untuk kasus ini sebenarnya ada alat yang lebih baik: int.TryParse(input, out int n) mengembalikan false alih-alih melempar exception. Exception ditujukan untuk situasi yang tidak diharapkan oleh kode; input yang sering tidak valid adalah hal yang diharapkan, jadi periksa input itu alih-alih menangkap exception.
Bagaimana exception bergerak
Exception yang dilempar jauh di dalam rantai pemanggilan akan keluar dari setiap method sampai menemukan handler yang cocok. Kode setelah titik throw di setiap method itu tidak dijalankan.
Output:
OrderTotal finished
5.00
Caught KeyNotFoundException in Main
PriceOf dan OrderTotal tidak punya catch, jadi exception langsung melewati keduanya menuju Main. Letakkan handler di tingkat yang tahu harus berbuat apa terhadap kegagalan itu, yang sering kali bukan di tempat kegagalan terjadi.
Menangkap exception tertentu dengan urutan yang benar
Klausa catch menangani tipenya dan setiap tipe turunannya. Kalau ada beberapa klausa, klausa pertama yang cocok yang dipakai, jadi urutkan dari yang paling spesifik ke yang paling umum:
Output:
10 / 2 = 5
Cannot divide by zero
Both values must be whole numbers
Unexpected: OverflowException
Pemanggilan terakhir melempar OverflowException, yang tidak ditangani oleh kedua klausa spesifik, jadi catch (Exception e) yang umum yang menanganinya. Kalau catch (Exception) diletakkan pertama, klausa setelahnya tidak akan pernah tercapai, dan compiler menolaknya dengan error CS0160.
Exception filter dengan when
C# 6 menambahkan when: kondisi yang menentukan apakah sebuah klausa catch berlaku. Kalau hasilnya false, runtime terus mencari handler lain seolah klausa itu tidak ada.
Output:
Not found: show an empty page
Server error 503: retry later
Unhandled by Call: HTTP 401
Filter memungkinkan kamu bercabang berdasarkan data di dalam exception tanpa menangkap lalu melemparnya ulang. Filter juga cara yang rapi untuk menangani dua tipe yang tidak berhubungan dengan cara yang sama, seperti yang dilakukan klausa ketiga. Filter berjalan sebelum stack di-unwind, jadi debugger atau crash dump tetap menunjukkan keadaan aslinya saat tidak ada filter yang cocok.
finally: kode yang selalu dijalankan
Blok finally dijalankan saat kontrol keluar dari try, entah karena selesai, kembali lebih awal, atau melempar exception. Di sinilah tempat untuk pembersihan. (Satu catatan: kalau exception tidak ditangkap di mana pun, proses bisa berakhir tanpa menjalankannya.)
Output:
Open connection
Close connection
finished
Open connection
Close connection
returned early
Open connection
Close connection
handled error
Di setiap kasus, "Close connection" dicetak sebelum hasil method sampai ke Main: finally berjalan setelah nilai return dihitung tetapi sebelum method benar-benar kembali. Sebuah try boleh punya finally tanpa catch sama sekali, yang membersihkan resource sambil membiarkan exception terus menuju pemanggil.
Untuk objek yang mengimplementasikan IDisposable (file, stream, koneksi), pernyataan using menuliskan try/finally ini untukmu.
Melempar ulang: throw; vs throw e;
Kadang sebuah blok catch mencatat sesuatu ke log lalu membiarkan exception terus berjalan. Cara kamu melempar ulang menentukan apakah stack trace-nya tetap utuh:
Output:
throw; trace mentions LoadConfig: True
throw e; trace mentions LoadConfig: False
throw e; memperlakukan exception seolah baru dilempar dari blok catch, jadi frame di bawahnya, termasuk method tempat error terjadi, hilang dari trace. Selalu lempar ulang dengan throw; saja. (Atribut NoInlining hanya ada karena JIT bisa menggabungkan method sekecil ini ke pemanggilnya, yang akan menyembunyikannya dari kedua trace.)
Untuk menambahkan konteks, bungkus exception dalam exception baru dan kirim yang asli sebagai inner exception: throw new ConfigException("Could not start the app", e);. Inner exception menyimpan stack trace-nya sendiri, dan logger mencetak seluruh rantainya. Cara menulis tipe exception sendiri dibahas di melempar exception.
Objek Exception
Setiap exception diturunkan dari System.Exception. Member yang paling sering dipakai:
| Member | Isinya |
|---|---|
Message | Deskripsi yang mudah dibaca manusia |
GetType().Name | Tipe exception, misalnya FormatException |
StackTrace | Rantai pemanggilan method di titik throw |
InnerException | Exception yang menyebabkan exception ini, atau null |
ToString() | Tipe, pesan, inner exception dan stack trace sekaligus |
Catat e.ToString() ke log, bukan e.Message, kalau kamu ingin mendiagnosis kegagalan nanti: pesannya saja jarang menyebutkan di mana masalahnya. Jangan tampilkan e.ToString() kepada pengguna akhir.
Tipe exception yang umum
| Exception | Penyebab umum |
|---|---|
NullReferenceException | Memanggil member pada referensi null |
ArgumentNullException | Method menerima null padahal butuh nilai |
ArgumentOutOfRangeException | Argumen atau indeks list di luar rentang yang diizinkan |
IndexOutOfRangeException | Indeks array di luar batasnya |
FormatException | int.Parse, DateTime.Parse dan sejenisnya pada teks dengan format yang salah |
InvalidCastException | Cast eksplisit ke tipe yang bukan tipe objek tersebut |
InvalidOperationException | Objek dalam keadaan yang salah untuk pemanggilan itu (sequence kosong, collection yang diubah) |
KeyNotFoundException | Membaca key dictionary yang tidak ada lewat indexer |
DivideByZeroException | Pembagian integer atau decimal dengan nol |
OverflowException | Angka hasil parse, konversi checked atau hasil aritmetika checked tidak muat di tipenya |
FileNotFoundException, IOException | Masalah sistem file |
NullReferenceException, IndexOutOfRangeException dan InvalidCastException hampir selalu berarti ada bug. Perbaiki kodenya, jangan menangkapnya.
Jangan menelan exception
Blok catch yang kosong menyembunyikan setiap error, termasuk yang tidak kamu duga:
try
{
SaveOrder(order);
}
catch (Exception)
{
// nothing: the order silently was not saved
}
Program terus berjalan seolah penyimpanannya berhasil, dan penyebab sebenarnya hilang. Pedoman agar penanganan exception tetap jujur:
- Tangkap hanya yang bisa kamu tangani, di tingkat yang bisa menanganinya.
- Kalau kamu menangkap untuk mencatat log, lempar ulang dengan
throw;kecuali program memang bisa lanjut. - Tangkap
Exceptionhanya di tepi terluar:Main, request handler, loop worker. - Utamakan
TryParse,TryGetValuedan pemeriksaannulldaripada exception untuk kasus yang sudah diperkirakan. Melempar exception lambat dibandingkan sebuah pemeriksaan, sedangkan bloktryyang tidak melempar apa pun hampir tidak ada biayanya.
Kesalahan umum
catch (Exception)di urutan pertama. Klausa setelahnya tidak bisa dicapai (CS0160).throw e;untuk melempar ulang. Ini menghapus stack trace aslinya; pakaithrow;.- Blok catch kosong. Error lenyap begitu saja; setidaknya catat ke log lalu lempar ulang.
- Memakai exception untuk alur kontrol. Validasi input dengan
TryParsealih-alih menangkapFormatException. - Menampilkan
e.Messagedari exception framework kepada pengguna. Kata-katanya berbeda antarversi .NET dan ditulis untuk developer.
Pertanyaan yang sering diajukan
Bagaimana cara kerja try catch di C#?
Kode yang mungkin gagal diletakkan di blok try. Kalau sebuah pernyataan di sana melempar exception, sisa blok dilewati dan runtime mencari klausa catch yang tipenya cocok dengan exception itu, pertama di method saat ini lalu di setiap pemanggilnya. catch pertama yang cocok dijalankan, lalu eksekusi berlanjut setelah seluruh pernyataan try.
Apakah finally selalu dijalankan di C#?
Hampir selalu: setelah blok try selesai dengan normal, setelah catch menangani exception, setelah return atau break di dalam blok, dan saat exception melewatinya menuju catch yang lebih atas di call stack. Blok ini tidak dijalankan kalau prosesnya berakhir lebih dulu: proses yang dimatikan, Environment.FailFast, StackOverflowException, dan di .NET Core dan sesudahnya, exception yang tidak ditangkap sama sekali, yang menghentikan proses sebelum blok finally sempat berjalan.
Bagaimana cara menangkap beberapa exception di C#?
Tulis beberapa klausa catch, mulai dari tipe yang paling spesifik: catch (FileNotFoundException) sebelum catch (IOException) sebelum catch (Exception). Compiler menolak klausa yang tidak pernah bisa dicapai karena klausa sebelumnya sudah menangkap tipenya. Untuk menangani dua tipe yang tidak berhubungan dengan cara yang sama, pakai filter: catch (Exception e) when (e is FormatException || e is OverflowException).
Apa bedanya throw dan throw ex di C#?
Di dalam catch, throw; melempar ulang exception saat ini dengan stack trace aslinya. throw ex; melempar objek yang sama tetapi mereset stack trace ke baris saat ini, sehingga method tempat error benar-benar terjadi hilang dari trace. Pakai throw;, atau bungkus exception-nya: throw new MyException("context", ex);.
Apa itu exception filter di C#?
Exception filter adalah klausa when setelah catch (C# 6 dan sesudahnya): catch (HttpRequestException e) when (e.Message.Contains("404")). Blok catch hanya dijalankan kalau kondisinya true; kalau tidak, exception terus mencari handler lain seolah klausa itu tidak ada, dan stack-nya tidak di-unwind.
Perlukah menangkap Exception di C#?
Hanya di tepi program: di bagian atas Main, di request handler, atau di loop latar belakang, tempat tugasnya adalah mencatat error ke log lalu lanjut atau keluar dengan rapi. Di bagian kode yang lebih dalam, tangkap tipe spesifik yang memang bisa kamu tangani, dan biarkan sisanya diteruskan.