Menangkap exception adalah separuh dari penanganan error; separuh lainnya adalah melempar exception yang tepat. Exception yang dipilih dengan baik memberi tahu pemanggil apa persisnya yang salah, dan apakah itu kesalahan mereka atau akibat keadaan program. Halaman ini membahas sisi melempar; try catch membahas penanganannya.
Pernyataan throw dan guard clause
throw menerima sebuah objek exception. Eksekusi method berhenti di situ, dan exception mencari handler ke atas lewat call stack. Penggunaan paling umum adalah guard clause: pemeriksaan di awal method yang menolak input tidak valid sebelum pekerjaan apa pun dilakukan.
Output:
ArgumentOutOfRangeException for parameter 'amount'
InvalidOperationException: The account is frozen.
Balance: 100
nameof(amount) menghasilkan string "amount" dan tetap benar kalau parameternya diganti nama. Exception argumen menyimpannya di ParamName, yang dipakai tool dan log untuk menunjuk argumen yang salah.
Guard clause membuat sisa method tetap sederhana: setelah pemeriksaan, kode boleh menganggap inputnya valid. Guard clause juga gagal tepat di titik kesalahan, alih-alih membiarkan nilai yang salah terus berjalan dan memicu NullReferenceException yang membingungkan tiga method kemudian.
Tipe exception mana yang dilempar
Pakai ulang tipe bawaan kalau tipe itu menggambarkan situasinya; pemanggil sudah tahu cara menanganinya.
| Situasi | Lempar |
|---|---|
Argumen yang wajib bernilai null | ArgumentNullException |
| Argumen di luar rentang yang diizinkan (jumlah negatif, indeks melewati akhir) | ArgumentOutOfRangeException |
| Argumen tidak valid dengan cara lain (nama kosong, ID dengan format salah) | ArgumentException |
| Pemanggilan tidak valid dalam keadaan objek saat ini | InvalidOperationException |
Operasinya memang tidak pernah didukung oleh tipe ini (Add pada collection read-only) | NotSupportedException |
| Method-nya belum ditulis | NotImplementedException |
Objek dipakai setelah Dispose | ObjectDisposedException |
| Operasi berbatas waktu kehabisan waktu | TimeoutException |
Batas antara tiga baris pertama dan InvalidOperationException adalah siapa yang perlu mengubah sesuatu. Exception argumen berkata "panggil dengan cara lain". InvalidOperationException berkata "pemanggilannya benar, tetapi bukan sekarang".
Jangan melempar Exception, SystemException atau ApplicationException secara langsung: pemanggil tidak bisa menangkapnya tanpa ikut menangkap semua exception lain. Jangan juga melempar NullReferenceException, IndexOutOfRangeException atau StackOverflowException sendiri; runtime menyediakan tipe itu untuk bug yang sungguhan.
Throw expression
Sebelum C# 7, throw hanya berupa pernyataan. Sejak C# 7, ia juga bisa muncul sebagai ekspresi di tiga tempat, sehingga pemeriksaan yang umum bisa ditulis dalam satu baris:
Output:
Ana <ana@example.com>
Null: name
ArgumentException: email
Perhatikan bahwa ArgumentNullException diturunkan dari ArgumentException, jadi catch (ArgumentException) yang diletakkan lebih dulu juga akan menangkap kasus null. Karena alasan yang sama, urutan klausa catch penting saat kamu menangani keduanya.
ThrowIfNull dan kawan-kawan (.NET 6 dan sesudahnya)
.NET modern menambahkan helper static yang menuliskan pemeriksaan dan throw untukmu, dengan nama parameter yang tertangkap otomatis:
public void Ship(Order order, int quantity, string address)
{
ArgumentNullException.ThrowIfNull(order); // .NET 6
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(quantity); // .NET 8
ArgumentException.ThrowIfNullOrWhiteSpace(address); // .NET 8
ObjectDisposedException.ThrowIf(disposed, this); // .NET 7
// ...
}
Helper ini berperilaku sama dengan if dan throw yang ditulis manual, dan membuat setiap guard clause cukup satu baris. Pada target yang lebih lama, tulis bentuk if yang ditunjukkan sebelumnya.
Menulis class exception kustom
Buat tipe exception sendiri saat pemanggil perlu menangkap kegagalan spesifik ini secara terpisah, atau saat handler membutuhkan data yang tidak bisa dibawa dengan baik oleh string pesan.
Output:
Cannot withdraw 25 from a balance of 15.
Short by 10
Konvensinya:
- Namanya diakhiri
Exception. - Class-nya diturunkan dari
Exception(atau dari tipe bawaan yang lebih spesifik saat ia merupakan kasus khusus dari tipe itu, sepertiInvalidOperationException). - Class-nya punya tiga constructor standar: tanpa argumen, dengan pesan, serta dengan pesan dan inner exception. Tambahkan constructor milikmu sendiri di atasnya.
- Data tambahan disimpan di property read-only yang diisi di constructor. Dengan begitu handler bisa bertindak berdasarkan
e.Requestedalih-alih mengurai pesannya.
Membungkus dengan inner exception
Saat kegagalan tingkat rendah perlu muncul sebagai kegagalan tingkat yang lebih tinggi, bungkus exception-nya. Exception aslinya disimpan sebagai InnerException, jadi tidak ada informasi yang hilang:
Output:
Setting 'port' must be a number, got '80a'.
Caused by: FormatException
Sekarang pemanggil berurusan dengan istilah konfigurasi, yang ia pahami, dan mencatat e.ToString() ke log akan mencetak seluruh rantainya, termasuk FormatException dan stack trace-nya. Bungkus hanya kalau kamu menambahkan makna; membungkus setiap exception dalam MyAppException yang generik hanya membuat handler harus menggali InnerException.
Melempar exception atau mengembalikan hasil
Exception ditujukan untuk kegagalan yang tidak diharapkan pemanggil dalam operasi normal. Untuk hasil yang rutin, seperti pencarian yang sering tidak menemukan apa pun atau input pengguna yang sering tidak valid, konvensi .NET adalah Try pattern: kembalikan bool dan serahkan nilainya lewat parameter out.
public bool TryWithdraw(decimal amount, out string error)
{
if (amount > Balance) { error = "Insufficient funds."; return false; }
Balance -= amount;
error = null;
return true;
}
Banyak tipe menyediakan keduanya: int.Parse melempar exception, int.TryParse mengembalikan false; dict[key] melempar exception, dict.TryGetValue mengembalikan false. Melempar exception jauh lebih mahal daripada mengembalikan nilai, jadi jangan letakkan di jalur yang berjalan ribuan kali per detik. Lihat ref dan out untuk parameter out.
Menulis pesan yang baik
Pesan exception dibaca oleh developer yang sedang melihat log. Buat pesannya menyebutkan apa yang salah dan, jika aman, nilai yang bermasalah: "Jumlah harus antara 1 dan 99, didapat 0." jauh lebih baik daripada "Input tidak valid." Tulis kalimat lengkap, dan jangan masukkan rahasia seperti password dan token ke dalam pesan, karena pesan itu akan berakhir di file log.
Kesalahan umum
- Melempar
Exceptionitu sendiri. Pemanggil tidak bisa menangkapnya secara selektif; pakai tipe yang spesifik. - Mengirim pesan di posisi nama parameter.
new ArgumentNullException("name")menerima nama parameter; pesannya ada di argumen kedua. - Nama parameter yang ditulis manual. Pakai
nameof(param)agar nama tetap benar saat diganti. - Exception kustom tanpa makna tambahan. Kalau ada tipe bawaan yang cocok, pakai tipe itu.
- Kehilangan error asli saat membungkus. Selalu kirim error itu sebagai inner exception.
Pertanyaan yang sering diajukan
Bagaimana cara melempar exception di C#?
Buat objek exception lalu lempar: throw new ArgumentException("Amount must be positive", nameof(amount));. Eksekusi berhenti di baris itu dan exception naik lewat call stack ke catch terdekat yang cocok. Pilih tipe bawaan paling spesifik yang menggambarkan masalahnya, atau tipe kustom saat pemanggil perlu menangani kasus ini secara terpisah.
Bagaimana cara membuat exception kustom di C#?
Turunkan class dari Exception dengan nama yang diakhiri Exception, lalu beri constructor standar: satu tanpa argumen, satu yang menerima pesan, dan satu yang menerima pesan serta inner exception, masing-masing memanggil constructor base(...) yang sesuai. Tambahkan property read-only untuk data yang dibutuhkan handler, misalnya ID pesanan atau saldo.
Kapan sebaiknya melempar ArgumentException dan kapan InvalidOperationException?
Lempar ArgumentException (atau ArgumentNullException / ArgumentOutOfRangeException) saat pemanggil mengirim nilai yang salah: perbaikannya adalah memanggil method dengan cara lain. Lempar InvalidOperationException saat argumennya benar tetapi objek sedang dalam keadaan yang salah untuk pemanggilan itu, misalnya membaca dari koneksi yang sudah ditutup atau menarik uang dari rekening yang dibekukan.
Apa itu throw expression di C#?
Sejak C# 7, throw bisa dipakai sebagai ekspresi di tiga tempat: setelah ??, sebagai salah satu cabang ?:, dan sebagai isi member atau lambda berbentuk expression body. Contohnya, _name = name ?? throw new ArgumentNullException(nameof(name)); menyimpan nilai atau melempar exception dalam satu baris.
Apa fungsi ArgumentNullException.ThrowIfNull?
Ini adalah helper static yang ditambahkan di .NET 6: ArgumentNullException.ThrowIfNull(customer); melempar ArgumentNullException dengan nama parameter yang terisi otomatis saat customer bernilai null, dan tidak melakukan apa pun jika sebaliknya. Versi berikutnya menambahkan helper serupa seperti ArgumentException.ThrowIfNullOrEmpty (.NET 7) dan ArgumentOutOfRangeException.ThrowIfNegative (.NET 8).