Tangkap beberapa pengecualian sekaligus?

2140

Dianjurkan untuk hanya menangkap System.Exception. Sebaliknya, hanya pengecualian "diketahui" yang harus ditangkap.

Sekarang, ini terkadang mengarah pada kode berulang yang tidak perlu, misalnya:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

Saya bertanya-tanya: Apakah ada cara untuk menangkap kedua pengecualian dan hanya menelepon satu WebId = Guid.Emptykali?

Contoh yang diberikan agak sederhana, karena hanya a GUID. Tetapi bayangkan kode di mana Anda memodifikasi objek beberapa kali, dan jika salah satu manipulasi gagal dengan cara yang diharapkan, Anda ingin "mengatur ulang" itu object. Namun, jika ada pengecualian yang tidak terduga, saya masih ingin membuang yang lebih tinggi.

Michael Stum
sumber
5
Jika Anda menggunakan .net 4 dan di atas, saya lebih suka menggunakan aggregateexception msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
Bepenfriends
2
Bepenfriends- Karena System.Guid tidak melempar AggregateException , alangkah baiknya jika Anda (atau seseorang) dapat memposting jawaban yang menunjukkan bagaimana Anda akan membungkusnya menjadi AggregateException dll.
weir
1
Saat menggunakan AggregateException: Melempar
AgregatException dalam
11
"Tidak disarankan hanya menangkap System.Exception." -dan jika metode dapat melempar 32 jenis pengecualian, apa yang dilakukan? tulis tangkapan untuk masing-masing secara terpisah?
giorgim
5
Jika suatu metode melempar 32 jenis pengecualian yang berbeda, itu ditulis dengan buruk. Entah itu tidak menangkap pengecualian dari panggilan yang dibuat sendiri, terlalu banyak melakukan FAR dalam satu metode, atau mayoritas / semuanya 32 harus merupakan pengecualian tunggal dengan kode alasan.
Flynn1179

Jawaban:

2100

Tangkap System.Exceptiondan aktifkan tipenya

catch (Exception ex)            
{                
    if (ex is FormatException || ex is OverflowException)
    {
        WebId = Guid.Empty;
        return;
    }

    throw;
}
Joseph Daigle
sumber
69
Sayangnya, FxCop (yaitu - Visual Studio Code Analysis) tidak menyukainya ketika Anda menangkap Exception.
Andrew Garrison
15
Saya setuju dengan tidak menangkap Pengecualian, tetapi dalam kasus ini, tangkapannya adalah filter. Anda mungkin memiliki lapisan lebih tinggi yang akan menangani jenis pengecualian lainnya. Saya akan mengatakan ini benar, meskipun itu termasuk tangkapan (Pengecualian x). Itu tidak mengubah aliran program, itu hanya menangani pengecualian tertentu kemudian membiarkan sisa aplikasi berurusan dengan jenis pengecualian lainnya.
lkg
28
Versi terbaru dari FxCop tidak memberikan pengecualian ketika kode di atas digunakan.
Peter
28
Tidak yakin apa yang salah dengan kode OP sejak awal. Jawaban # 1 yang diterima hampir dua kali lebih banyak baris dan jauh lebih mudah dibaca.
João Bragança
22
@ JoãoBragança: Walaupun jawaban dalam contoh ini menggunakan lebih banyak baris, coba bayangkan jika Anda berurusan dengan file IO misalnya, dan semua yang ingin Anda lakukan adalah menangkap pengecualian itu dan melakukan beberapa pesan log, tetapi hanya yang Anda harapkan berasal dari Anda mengajukan metode IO. Maka Anda sering harus berurusan dengan jumlah pengecualian yang lebih besar (sekitar 5 atau lebih). Dalam situasi itu, pendekatan ini dapat menghemat beberapa baris.
Xilconic
595

EDIT: Saya setuju dengan orang lain yang mengatakan bahwa, pada C # 6.0, filter pengecualian sekarang merupakan cara yang baik untuk digunakan:catch (Exception ex) when (ex is ... || ex is ... )

Kecuali bahwa saya masih agak benci tata letak garis panjang dan secara pribadi akan mengeluarkan kode seperti berikut. Saya pikir ini fungsional seperti estetika, karena saya percaya ini meningkatkan pemahaman. Beberapa mungkin tidak setuju:

catch (Exception ex) when (
    ex is ...
    || ex is ...
    || ex is ...
)

ASLI:

Saya tahu saya sedikit terlambat ke pesta di sini, tetapi asap suci ...

Memotong langsung ke pengejaran, jenis ini menduplikasi jawaban sebelumnya, tetapi jika Anda benar-benar ingin melakukan tindakan umum untuk beberapa jenis pengecualian dan menjaga semuanya tetap rapi dan rapi dalam ruang lingkup satu metode, mengapa tidak hanya menggunakan lambda / closure / inline berfungsi untuk melakukan sesuatu seperti berikut ini? Maksud saya, peluangnya cukup bagus sehingga Anda akhirnya menyadari bahwa Anda hanya ingin membuat penutupan itu sebagai metode terpisah yang dapat Anda manfaatkan di semua tempat. Tetapi kemudian akan sangat mudah untuk melakukan itu tanpa benar-benar mengubah sisa kode secara struktural. Baik?

private void TestMethod ()
{
    Action<Exception> errorHandler = ( ex ) => {
        // write to a log, whatever...
    };

    try
    {
        // try some stuff
    }
    catch ( FormatException  ex ) { errorHandler ( ex ); }
    catch ( OverflowException ex ) { errorHandler ( ex ); }
    catch ( ArgumentNullException ex ) { errorHandler ( ex ); }
}

Saya tidak bisa menahan diri untuk bertanya-tanya ( peringatan: sedikit ironi / sarkasme di muka) mengapa harus melakukan semua upaya ini pada dasarnya hanya mengganti yang berikut ini:

try
{
    // try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}

... dengan beberapa variasi gila dari bau kode selanjutnya, maksud saya contoh, hanya untuk berpura-pura bahwa Anda menyimpan beberapa penekanan tombol.

// sorta sucks, let's be honest...
try
{
    // try some stuff
}
catch( Exception ex )
{
    if (ex is FormatException ||
        ex is OverflowException ||
        ex is ArgumentNullException)
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

Karena itu tentu saja tidak secara otomatis lebih mudah dibaca.

Memang, saya meninggalkan tiga contoh identik dari /* write to a log, whatever... */ return;contoh pertama.

Tapi itu maksud saya. Kalian pernah mendengar fungsi / metode, kan? Serius. Tulis ErrorHandlerfungsi umum dan, seperti, panggil dari masing-masing blok tangkapan.

Jika Anda bertanya kepada saya, contoh kedua (dengan ifdan iskata kunci) keduanya kurang terbaca secara signifikan, dan secara bersamaan lebih rentan terhadap kesalahan selama fase pemeliharaan proyek Anda.

Fase pemeliharaan, bagi siapa saja yang mungkin relatif baru dalam pemrograman, akan mencapai 98,7% atau lebih dari keseluruhan masa hidup proyek Anda, dan orang bodoh yang melakukan pemeliharaan hampir pasti akan menjadi orang lain selain Anda. Dan ada kemungkinan yang sangat baik bahwa mereka akan menghabiskan 50% dari waktu mereka untuk pekerjaan mengutuk nama Anda.

Dan tentu saja FxCop menggonggong pada Anda dan Anda juga harus menambahkan atribut ke kode Anda yang memiliki persis zip terkait dengan program yang sedang berjalan, dan hanya ada di sana untuk memberitahu FxCop untuk mengabaikan masalah bahwa dalam 99,9% kasus itu benar-benar benar dalam menandai. Dan, maaf, saya mungkin salah, tetapi bukankah atribut "abaikan" itu benar-benar dikompilasi ke dalam aplikasi Anda?

Apakah menempatkan seluruh iftes pada satu baris membuatnya lebih mudah dibaca? Saya kira tidak. Maksud saya, saya punya programmer lain yang dengan keras berdebat sekali dulu bahwa menempatkan lebih banyak kode pada satu baris akan membuatnya "berjalan lebih cepat." Tapi tentu saja dia murka. Mencoba menjelaskan kepadanya (dengan wajah lurus - yang menantang) bagaimana penerjemah atau kompiler akan memecah garis panjang itu menjadi pernyataan satu-instruksi-per-garis yang terpisah - pada dasarnya identik dengan hasilnya jika ia terus maju dan hanya membuat kode dapat dibaca alih-alih mencoba mengompilasi kompiler - tidak berpengaruh padanya. Tapi saya ngelantur.

Berapa banyak kurang dibaca tidak mendapatkan ini ketika Anda menambahkan tiga jenis pengecualian lebih, satu atau dua bulan dari sekarang? (Jawaban: itu mendapat banyak kurang dibaca).

Salah satu poin utama, sungguh, adalah bahwa sebagian besar titik memformat kode sumber tekstual yang kita semua lihat setiap hari adalah untuk membuatnya benar-benar jelas bagi manusia lain apa yang sebenarnya terjadi ketika kode dijalankan. Karena kompiler mengubah kode sumber menjadi sesuatu yang sama sekali berbeda dan tidak peduli tentang gaya pemformatan kode Anda. Jadi all-on-one-line benar-benar menyebalkan juga.

Hanya mengatakan ...

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}
Craig
sumber
36
Ketika saya pertama kali menemukan pertanyaan ini, saya sudah menerima semua jawaban yang diterima. Keren saya hanya bisa menangkap semua Exceptiondan memeriksa tipenya. Saya pikir itu membersihkan kode, tetapi sesuatu membuat saya kembali ke pertanyaan dan saya benar-benar membaca jawaban lain untuk pertanyaan itu. Saya mengunyahnya sebentar, tetapi saya harus setuju dengan Anda. Hal ini lebih mudah dibaca dan dipertahankan untuk menggunakan fungsi untuk mengeringkan kode Anda daripada menangkap segala sesuatu, periksa jenis membandingkan terhadap daftar, kode pembungkus, dan lempar. Terima kasih telah datang terlambat dan menyediakan opsi alternatif dan waras (IMO). +1.
Galat
8
Menggunakan fungsi penanganan kesalahan tidak akan berfungsi jika Anda ingin menyertakan a throw;. Anda harus mengulangi baris kode di setiap blok tangkap (jelas bukan akhir dunia tetapi layak disebutkan karena itu kode yang perlu diulang).
kad81
5
@ kad81, itu benar, tetapi Anda masih akan mendapat manfaat dari penulisan kode logging dan pembersihan di satu tempat, dan mengubahnya di satu tempat jika perlu, tanpa semantik yang bodoh untuk menangkap basis Jenis pengecualian lalu bercabang berdasarkan jenis pengecualian. Dan satu throw();pernyataan tambahan di setiap blok tangkap adalah harga kecil yang harus dibayar, IMO, dan masih membuat Anda berada dalam posisi untuk melakukan pembersihan pengecualian jenis khusus tambahan jika perlu.
Craig
2
Hai @ Reitffunk, gunakan saja Func<Exception, MyEnumType>alih-alih Action<Exception>. Itu Func<T, Result>, dengan Resultmenjadi tipe pengembalian.
Craig
3
Saya setuju sepenuhnya di sini. Saya juga membaca jawaban pertama dan pemikiran itu tampaknya masuk akal. Pindah ke generik 1 untuk semua penangan pengecualian. Sesuatu dalam diri saya membuat saya muntah secara internal ... jadi saya mengembalikan kodenya. Lalu datanglah keindahan ini! Ini perlu jawaban yang diterima
Conor Gallagher
372

Seperti yang telah ditunjukkan orang lain, Anda dapat memiliki ifpernyataan di dalam blok penangkap untuk menentukan apa yang sedang terjadi. C # 6 mendukung Filter Pengecualian, jadi yang berikut ini akan berfungsi:

try {  }
catch (Exception e) when (MyFilter(e))
{
    
}

The MyFilterMetode kemudian bisa terlihat seperti ini:

private bool MyFilter(Exception e)
{
  return e is ArgumentNullException || e is FormatException;
}

Atau, ini semua bisa dilakukan inline (sisi kanan pernyataan when hanya harus menjadi ekspresi boolean).

try {  }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
    
}

Ini berbeda dari menggunakan ifpernyataan dari dalam catchblok, menggunakan filter pengecualian tidak akan melepaskan tumpukan.

Anda dapat mengunduh Visual Studio 2015 untuk melihatnya.

Jika Anda ingin terus menggunakan Visual Studio 2013, Anda dapat menginstal paket nuget berikut:

Instal-Paket Microsoft.Net.Compiler

Pada saat penulisan, ini akan termasuk dukungan untuk C # 6.

Merujuk paket ini akan menyebabkan proyek dibangun menggunakan versi spesifik dari kompiler C # dan Visual Basic yang terdapat dalam paket, yang bertentangan dengan versi sistem yang diinstal.

Joe
sumber
3
Dengan sabar menunggu rilis resmi 6 ... Saya ingin melihat ini mendapatkan cekak ketika itu terjadi.
RubberDuck
@RubberDuck Saya sangat ingin untuk operator propagasi nol dari C # 6. Mencoba meyakinkan seluruh tim saya bahwa risiko bahasa yang tidak stabil / kompiler sepadan. Banyak perbaikan kecil dengan dampak besar. Mengenai ditandai sebagai jawaban, tidak penting, selama orang-orang menyadari ini akan / mungkin, saya senang.
Joe
Baik?! Saya akan memperhatikan basis kode saya dalam waktu dekat. =) Saya tahu cek itu tidak penting, tetapi mengingat jawaban yang diterima akan segera kedaluwarsa, saya berharap OP kembali untuk memeriksa ini untuk memberikan visibilitas yang tepat.
RubberDuck
Itu sebabnya saya belum memberikannya @Joe. Saya ingin ini terlihat. Anda mungkin ingin menambahkan contoh filter sebaris untuk kejelasan.
RubberDuck
188

Sayangnya tidak dalam C #, karena Anda memerlukan filter pengecualian untuk melakukannya dan C # tidak mengekspos fitur MSIL itu. VB.NET memang memiliki kemampuan ini, misalnya

Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException

Apa yang bisa Anda lakukan adalah menggunakan fungsi anonim untuk merangkum kode kesalahan Anda, dan kemudian menyebutnya di blok tangkapan khusus tersebut:

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}
Greg Beech
sumber
26
Ide menarik dan contoh lain bahwa VB.net memiliki beberapa kelebihan menarik dibandingkan C # terkadang
Michael Stum
47
@MichaelStum dengan yang jenis sintaks aku tidak akan menyebutnya semenarik sama sekali ... bergidik
MarioDS
17
Filter pengecualian tersedia dalam c # 6! Perhatikan perbedaan penggunaan filter yang mendukung rethrowing roslyn.codeplex.com/discussions/541301
Arne Deruwe
@ArneDeruwe Terima kasih atas tautannya! Saya baru belajar satu alasan penting lagi untuk tidak melempar-ulang: throw e;menghancurkan stacktrace dan callstack, throw;menghancurkan "hanya" callstack (menjadikan crash-dumps tidak berguna!) Alasan yang sangat baik untuk digunakan maupun jika dapat dihindari!
AnorZaken
1
Sampai C # 6 filter pengecualian tersedia! Akhirnya.
Danny
134

Demi kelengkapan, karena .NET 4.0 kode dapat ditulis ulang sebagai:

Guid.TryParse(queryString["web"], out WebId);

TryParse tidak pernah melempar pengecualian dan mengembalikan false jika formatnya salah, mengatur WebId menjadi Guid.Empty.


Karena C # 7 Anda dapat menghindari memperkenalkan variabel pada baris terpisah:

Guid.TryParse(queryString["web"], out Guid webId);

Anda juga dapat membuat metode untuk mem-parsing tupel yang kembali, yang belum tersedia di .NET Framework pada versi 4.6:

(bool success, Guid result) TryParseGuid(string input) =>
    (Guid.TryParse(input, out Guid result), result);

Dan gunakan seperti ini:

WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;

Pembaruan tidak berguna berikutnya untuk jawaban tidak berguna ini muncul ketika dekonstruksi parameter-out diimplementasikan dalam C # 12. :)

Athari
sumber
19
Tepat - ringkas, dan Anda benar-benar melewati hukuman kinerja menangani pengecualian, bentuk buruk sengaja menggunakan pengecualian untuk mengontrol aliran program, dan fokus lembut agar logika konversi Anda menyebar, sedikit di sini dan sedikit di sana .
Craig
9
Saya tahu apa yang Anda maksudkan, tetapi tentu saja Guid.TryParsetidak pernah kembali Guid.Empty. Jika string berada dalam format yang salah, ia mengatur resultparameter output Guid.Empty, tetapi kembali false . Saya menyebutkannya karena saya telah melihat kode yang melakukan hal-hal dalam gaya Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }, yang biasanya salah jika sbisa menjadi representasi string Guid.Empty.
14
wow Anda telah menjawab pertanyaan, kecuali bahwa itu bukan semangat pertanyaan. Masalah yang lebih besar adalah sesuatu yang lain :(
nawfal
6
Pola yang tepat untuk menggunakan TryParse, tentu saja, lebih seperti if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ }, yang tidak meninggalkan ambiguitas seperti contoh rusak di mana nilai input mungkin sebenarnya representasi string dari Guid.
Craig
2
Jawaban ini mungkin memang benar dalam kaitannya dengan Guid.Parse, tetapi telah melewatkan seluruh inti pertanyaan aslinya. Yang tidak ada hubungannya dengan Guid.Parse, tetapi dalam hal menangkap Exception vs FormatException / OverflowException / etc.
Conor Gallagher
115

Filter pengecualian sekarang tersedia dalam c # 6+. Anda dapat melakukan

try
{
       WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
     WebId = Guid.Empty;
}

Di C # 7.0+, Anda dapat menggabungkan ini dengan pencocokan pola juga

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae &&
                           ae.InnerExceptions.Count > tasks.Count/2)
{
   //More than half of the tasks failed maybe..? 
}
Mat J
sumber
Metode ini lebih disukai tidak hanya karena sederhana dan jelas, tetapi juga tidak harus melepas tumpukan jika kondisi tidak terpenuhi, yang memberikan kinerja dan informasi diagnostik yang lebih baik dibandingkan dengan rethrow.
joe
74

Jika Anda dapat meningkatkan aplikasi ke C # 6, Anda beruntung. Versi C # yang baru telah menerapkan filter Pengecualian. Jadi Anda bisa menulis ini:

catch (Exception ex) when (ex is FormatException || ex is OverflowException) {
    WebId = Guid.Empty;
}

Beberapa orang berpikir kode ini sama dengan

catch (Exception ex) {                
    if (ex is FormatException || ex is OverflowException) {
        WebId = Guid.Empty;
    }
    throw;
}

Tapi ternyata tidak. Sebenarnya ini adalah satu-satunya fitur baru di C # 6 yang tidak mungkin ditiru di versi sebelumnya. Pertama, lemparan ulang berarti lebih banyak overhead daripada melewatkan tangkapan. Kedua, itu tidak setara secara semantik. Fitur baru mempertahankan tumpukan utuh ketika Anda men-debug kode Anda. Tanpa fitur ini, dump crash kurang berguna atau bahkan tidak berguna.

Lihat diskusi tentang ini di CodePlex . Dan contoh menunjukkan perbedaannya .

Maniero
sumber
4
Melempar tanpa kecuali menjaga stack, tetapi "throw ex" akan menimpanya.
Ivan
32

Jika Anda tidak ingin menggunakan ifpernyataan dalam catchlingkup, di C# 6.0Anda dapat menggunakan Exception Filterssintaks yang sudah didukung oleh CLR dalam versi preview tetapi hanya ada di VB.NET/ MSIL:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

Kode ini akan menangkap Exceptionhanya ketika itu InvalidDataExceptionatauArgumentNullException .

Sebenarnya, pada dasarnya Anda dapat meletakkan segala kondisi di dalam whenklausa itu:

static int a = 8;

...

catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
    Console.WriteLine("Catch");
}

Perhatikan bahwa sebagai lawan dari ifpernyataan di dalam catchruang lingkup, Exception Filterstidak bisa melempar Exceptions, dan ketika mereka melakukannya, atau ketika kondisinya tidak true, catchkondisi selanjutnya akan dievaluasi sebagai gantinya:

static int a = 7;

static int b = 0;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Output: Tangkapan umum.

Ketika ada lebih dari satu true Exception Filter- yang pertama akan diterima:

static int a = 8;

static int b = 4;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Output: Tangkap.

Dan seperti yang Anda lihat dalam MSILkode tidak diterjemahkan ke ifpernyataan, tetapi untuk Filters, dan Exceptionstidak dapat dilempar dari dalam area yang ditandai dengan Filter 1dan Filter 2tetapi filter yang melempar Exceptionakan gagal sebagai gantinya, juga nilai perbandingan terakhir didorong ke stack sebelum endfilterperintah akan menentukan keberhasilan / kegagalan filter ( Catch 1 XOR Catch 2 akan mengeksekusi sesuai):

Filter Pengecualian MSIL

Juga, secara khusus Guidmemiliki Guid.TryParsemetode.

Tamir Vered
sumber
+1 untuk menampilkan beberapa filter saat dan memberikan penjelasan tentang apa yang terjadi ketika beberapa filter digunakan.
steven87vt
26

Dengan C # 7 jawaban dari Michael Stum dapat ditingkatkan sambil menjaga keterbacaan pernyataan switch:

catch (Exception ex)
{
    switch (ex)
    {
        case FormatException _:
        case OverflowException _:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}

Dan dengan C # 8 sebagai ekspresi switch:

catch (Exception ex)
{
    WebId = ex switch
    {
        _ when ex is FormatException || ex is OverflowException => Guid.Empty,
        _ => throw ex
    };
}
Fabian
sumber
3
Ini harus menjadi jawaban yang diterima pada 2018 IMHO.
MemphiZ
6
Jawaban Mat J menggunakan whenjauh lebih elegan / sesuai daripada saklar.
rgoliveira
@ rgoliveira: Saya setuju bahwa untuk kasus yang ditanyakan dalam pertanyaan, jawaban oleh Mat J lebih elegan dan sesuai. Namun, sulit dibaca jika Anda memiliki kode berbeda yang ingin Anda jalankan tergantung pada jenis pengecualian atau jika Anda ingin benar-benar menggunakan contoh pengecualian. Semua skenario ini dapat diperlakukan sama dengan pernyataan switch ini.
Fabian
1
@Fabian "jika Anda memiliki kode berbeda yang ingin Anda jalankan tergantung pada jenis pengecualian atau jika Anda ingin benar-benar menggunakan contoh pengecualian", maka Anda hanya membuat catchblok yang berbeda , atau Anda tetap harus melemparkannya .. Dalam pengalaman saya, a throw;di catchblok Anda mungkin bau kode.
rgoliveira
@rgoliveira: Menggunakan melempar blok menangkap adalah OK dalam beberapa kasus melihat link di . Karena pernyataan kasus benar-benar menggunakan tautan pencocokan pola, Anda tidak perlu melakukan casting jika Anda mengganti tautan operator pembuangan (garis bawah) dengan nama variabel. Jangan salah paham, saya setuju dengan Anda bahwa filter pengecualian adalah cara yang lebih bersih untuk melakukannya, tetapi beberapa blok penangkap menambah banyak kurung keriting.
Fabian
20

Jawaban yang diterima tampaknya dapat diterima, kecuali bahwa CodeAnalysis / FxCop akan mengeluh tentang fakta bahwa itu menangkap jenis pengecualian umum.

Juga, tampaknya operator "adalah" mungkin menurunkan kinerja sedikit.

CA1800: Jangan membuang yang tidak perlu kata yang untuk "pertimbangkan menguji hasil dari operator 'sebagai' sebagai gantinya," tetapi jika Anda melakukannya, Anda akan menulis lebih banyak kode daripada jika Anda menangkap setiap pengecualian secara terpisah.

Bagaimanapun, inilah yang akan saya lakukan:

bool exThrown = false;

try
{
    // Something
}
catch (FormatException) {
    exThrown = true;
}
catch (OverflowException) {
    exThrown = true;
}

if (exThrown)
{
    // Something else
}
Mat
sumber
19
Namun ketahuilah bahwa Anda tidak dapat mengubah kembali pengecualian tanpa kehilangan jejak tumpukan jika Anda melakukannya seperti ini. (Lihat komentar Michael Stum pada jawaban yang diterima)
René
2
Pola ini dapat diperbaiki dengan menyimpan pengecualian (mohon maafkan pemformatan yang buruk - Saya tidak tahu cara memasukkan kode ke dalam komentar): Pengecualian ex = null; coba {// something} catch (FormatException e) {ex = e; } catch (OverflowException e) {ex = e; } if (ex! = null) {// sesuatu yang lain dan berurusan dengan ex}
Jesse Weigert
3
@JesseWeigert: 1. Anda dapat menggunakan backticks untuk memberikan sepotong teks font satu spasi dan latar belakang abu-abu terang. 2. Anda masih tidak dapat mengubah kembali pengecualian asli termasuk stacktrace .
Oliver
2
@CleverNeologism walaupun mungkin benar bahwa menggunakan isoperator mungkin memiliki sedikit dampak negatif pada kinerja, juga benar bahwa pengendali pengecualian bukan tempat yang terlalu khawatir untuk mengoptimalkan kinerja. Jika aplikasi Anda menghabiskan begitu banyak waktu dalam penangan pengecualian sehingga optimasi kinerja di sana akan membuat perbedaan nyata dalam kinerja aplikasi, maka ada masalah kode lain yang perlu diperhatikan. Karena itu, saya masih tidak suka solusi ini karena Anda kehilangan jejak tumpukan dan karena pembersihan secara kontekstual dihapus dari pernyataan tangkapan.
Craig
3
Satu-satunya waktu isoperator menurunkan kinerja adalah jika Anda kemudian melakukan asoperasi (karenanya mereka memenuhi syarat dengan tidak perlu ). Jika yang Anda lakukan hanyalah menguji gips tanpa benar-benar perlu melakukan gips, maka isoperatorlah yang tepat yang ingin Anda gunakan.
bernilai
19

di C # 6 pendekatan yang disarankan adalah menggunakan Filter Pengecualian, berikut ini sebuah contoh:

 try
 {
      throw new OverflowException();
 }
 catch(Exception e ) when ((e is DivideByZeroException) || (e is OverflowException))
 {
       // this will execute iff e is DividedByZeroEx or OverflowEx
       Console.WriteLine("E");
 }
SHM
sumber
18

Ini adalah varian dari jawaban Matt (saya merasa ini sedikit lebih bersih) ... gunakan metode:

public void TryCatch(...)
{
    try
    {
       // something
       return;
    }
    catch (FormatException) {}
    catch (OverflowException) {}

    WebId = Guid.Empty;
}

Pengecualian lain akan dilemparkan dan kode WebId = Guid.Empty;tidak akan terkena. Jika Anda tidak ingin pengecualian lain merusak program Anda, tambahkan saja ini SETELAH dua tangkapan lainnya:

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}
bsara
sumber
-1 Ini akan dieksekusi WebId = Guid.Emtpydalam kasus di mana tidak ada pengecualian dilemparkan.
Sepster
4
@sepster Saya pikir pernyataan kembali setelah "// something" tersirat di sini. Saya tidak terlalu suka solusinya, tetapi ini adalah varian konstruktif dalam diskusi. +1 untuk membatalkan downvote Anda :-)
toong
@Sepster toong benar, saya berasumsi bahwa jika Anda ingin kembali ke sana, maka Anda akan menempatkan satu ... Saya mencoba untuk membuat jawaban saya cukup umum untuk diterapkan pada semua situasi jika orang lain dengan pertanyaan yang serupa tetapi tidak tepat akan mendapat manfaat sebagai baik. Namun, untuk ukuran yang baik, saya telah menambahkan returnjawaban saya. Terima kasih atas masukannya.
bsara
18

Jawaban Joseph Daigle adalah solusi yang baik, tetapi saya menemukan struktur berikut menjadi sedikit lebih rapi dan lebih sedikit kesalahan.

catch(Exception ex)
{   
    if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

Ada beberapa keuntungan dari membalikkan ekspresi:

  • Pernyataan pengembalian tidak diperlukan
  • Kode tidak bersarang
  • Tidak ada risiko melupakan pernyataan 'melempar' atau 'kembali' yang dalam solusi Joseph dipisahkan dari ekspresi.

Ia bahkan dapat dipadatkan menjadi satu baris (meskipun tidak terlalu cantik)

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

Edit: The penyaringan pengecualian di C # 6.0 akan membuat sintaks sedikit lebih bersih dan dilengkapi dengan sejumlah manfaat lain atas setiap solusi saat ini. (terutama meninggalkan tumpukan tanpa terluka)

Berikut adalah bagaimana masalah yang sama akan terlihat menggunakan sintaks C # 6.0:

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}
Stefan T
sumber
2
+1, ini adalah jawaban terbaik. Ini lebih baik daripada jawaban teratas karena kebanyakan tidak ada return, meskipun membalikkan kondisi juga sedikit lebih baik.
DCShannon
Aku bahkan tidak memikirkan itu. Tangkapan yang bagus, saya akan menambahkannya ke daftar.
Stefan T
16

@Micheal

Versi kode Anda yang sedikit direvisi:

catch (Exception ex)
{
   Type exType = ex.GetType();
   if (exType == typeof(System.FormatException) || 
       exType == typeof(System.OverflowException)
   {
       WebId = Guid.Empty;
   } else {
      throw;
   }
}

Perbandingan string jelek dan lambat.

FlySwat
sumber
21
Mengapa tidak menggunakan kata kunci "is"?
Chris Pietschmann
29
@Michael - Jika Microsoft memperkenalkan, katakanlah, StringTooLongException berasal dari FormatException maka itu masih merupakan pengecualian format, hanya yang spesifik. Itu tergantung apakah Anda ingin semantik 'menangkap jenis pengecualian yang tepat ini' atau 'menangkap pengecualian yang berarti format string salah'.
Greg Beech
6
@Michael - Juga, perhatikan bahwa "catch (FormatException ex) memiliki semantik yang terakhir, ia akan menangkap apa pun yang berasal dari FormatException.
Greg Beech
14
@Alex No. "throw" tanpa "ex" membawa pengecualian asli, termasuk jejak tumpukan asli, ke atas. Menambahkan "ex" membuat pelacakan jejak reset, sehingga Anda benar-benar mendapatkan pengecualian yang berbeda dari yang asli. Saya yakin orang lain bisa menjelaskannya lebih baik dari saya. :)
Samantha Branham
13
-1: Kode ini sangat rapuh - pengembang perpustakaan dapat berharap untuk mengganti throw new FormatException();dengan throw new NewlyDerivedFromFormatException();tanpa melanggar kode menggunakan perpustakaan, dan itu akan berlaku untuk semua kasus penanganan pengecualian kecuali di mana seseorang menggunakan ==bukan is(atau hanya catch (FormatException)).
Sam Harwell
13

Bagaimana tentang

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
Maurice
sumber
Itu hanya berfungsi jika Catch-Code dapat sepenuhnya dipindahkan ke Try-Block. Tetapi pencitraan kode tempat Anda membuat banyak manipulasi ke suatu objek, dan satu di tengah gagal, dan Anda ingin "mengatur ulang" objek.
Michael Stum
4
Dalam hal ini saya akan menambahkan fungsi reset dan menyebutnya dari beberapa blok tangkapan.
Maurice
12
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}
Konstantin Spirin
sumber
11

Hati-hati dan Diperingatkan: Jenis fungsional fungsional yang lain.

Apa yang ada di tautan tidak menjawab pertanyaan Anda secara langsung, tetapi sepele untuk memperluasnya agar terlihat seperti:

static void Main() 
{ 
    Action body = () => { ...your code... };

    body.Catch<InvalidOperationException>() 
        .Catch<BadCodeException>() 
        .Catch<AnotherException>(ex => { ...handler... })(); 
}

(Pada dasarnya kosongkan yang lain Catch kelebihan yang mengembalikan sendiri)

Pertanyaan yang lebih besar adalah mengapa . Saya tidak berpikir biaya melebihi keuntungan di sini :)

nawfal
sumber
1
Satu keuntungan yang mungkin dari pendekatan ini adalah bahwa ada perbedaan semantik antara menangkap dan memikirkan kembali suatu pengecualian versus tidak menangkapnya; dalam beberapa kasus, kode harus bertindak berdasarkan pengecualian tanpa menangkapnya. Hal seperti itu dimungkinkan di vb.net, tetapi tidak dalam C # kecuali seseorang menggunakan pembungkus yang ditulis dalam vb.net dan dipanggil dari C #.
supercat
1
Bagaimana cara bertindak dengan pengecualian tanpa menangkapnya? Saya tidak sepenuhnya mengerti Anda.
nawfal
@nawful ... menggunakan vb filter - function filt (ex as exception): LogEx (ex): return false ... lalu di baris catch: catch ex saat filt (ex)
FastAl
1
@ FastAl Bukankah ini yang diizinkan oleh pengecualian-filter dalam C # 6?
HimBromBeere
@HimBromBeere ya mereka analog langsung
FastAl
9

Pembaruan 2015-12-15: Lihat https://stackoverflow.com/a/22864936/1718702 untuk C # 6. Ini lebih bersih dan sekarang standar dalam bahasa.

Diarahkan untuk orang yang menginginkan solusi yang lebih elegan untuk menangkap sekali dan memfilter pengecualian, saya menggunakan metode ekstensi seperti yang ditunjukkan di bawah ini.

Saya sudah memiliki ekstensi ini di perpustakaan saya, awalnya ditulis untuk tujuan lain, tetapi bekerja dengan sempurna untuk typememeriksa pengecualian. Plus, imho, itu terlihat lebih bersih daripada banyak ||pernyataan. Juga, tidak seperti jawaban yang diterima, saya lebih suka penanganan eksepsi eksplisit sehingga ex is ...memiliki perilaku yang tidak diinginkan karena kelas derivasi ditugaskan ke tipe induknya).

Pemakaian

if (ex.GetType().IsAnyOf(
    typeof(FormatException),
    typeof(ArgumentException)))
{
    // Handle
}
else
    throw;

Ekstensi IsAnyOf.cs (Lihat Contoh Penanganan Kesalahan Lengkap untuk Ketergantungan)

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }
    }
}

Contoh Penanganan Kesalahan Lengkap (Salin-Tempel ke aplikasi Konsol baru)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;

namespace IsAnyOfExceptionHandlerSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // High Level Error Handler (Log and Crash App)
            try
            {
                Foo();
            }
            catch (OutOfMemoryException ex)
            {
                Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
                Console.ReadKey();
            }
        }

        static void Foo()
        {
            // Init
            List<Action<string>> TestActions = new List<Action<string>>()
            {
                (key) => { throw new FormatException(); },
                (key) => { throw new ArgumentException(); },
                (key) => { throw new KeyNotFoundException();},
                (key) => { throw new OutOfMemoryException(); },
            };

            // Run
            foreach (var FooAction in TestActions)
            {
                // Mid-Level Error Handler (Appends Data for Log)
                try
                {
                    // Init
                    var SomeKeyPassedToFoo = "FooParam";

                    // Low-Level Handler (Handle/Log and Keep going)
                    try
                    {
                        FooAction(SomeKeyPassedToFoo);
                    }
                    catch (Exception ex)
                    {
                        if (ex.GetType().IsAnyOf(
                            typeof(FormatException),
                            typeof(ArgumentException)))
                        {
                            // Handle
                            Console.WriteLine("ex was {0}", ex.GetType().Name);
                            Console.ReadKey();
                        }
                        else
                        {
                            // Add some Debug info
                            ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
                            throw;
                        }
                    }
                }
                catch (KeyNotFoundException ex)
                {
                    // Handle differently
                    Console.WriteLine(ex.Message);

                    int Count = 0;
                    if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
                        foreach (var Key in ex.Data.Keys)
                            Console.WriteLine(
                                "[{0}][\"{1}\" = {2}]",
                                Count, Key, ex.Data[Key]);

                    Console.ReadKey();
                }
            }
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }

        /// <summary>
        /// Validates if any passed in parameter is equal to null.
        /// </summary>
        /// <param name="p_parameters">Parameters to test for Null.</param>
        /// <returns>True if one or more parameters are null.</returns>
        public static bool IsAnyNull(params object[] p_parameters)
        {
            p_parameters
                .CannotBeNullOrEmpty("p_parameters");

            foreach (var item in p_parameters)
                if (item == null)
                    return true;

            return false;
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void CannotBeNull(this object p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw
                    new
                        ArgumentNullException(
                        string.Format("Parameter \"{0}\" cannot be null.",
                        p_name), default(Exception));
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));

            if (p_parameter.Count <= 0)
                throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
        }

        /// <summary>
        /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentException"></exception>
        public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
        {
            if (string.IsNullOrEmpty(p_parameter))
                throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
        }
    }
}

Dua Sampel Unit Tes NUnit

Perilaku mencocokkan untuk Exceptiontipe adalah tepat (mis. Seorang anak BUKAN pasangan yang cocok dengan tipe induknya).

using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;

namespace UnitTests.Common.Fluent_Validations
{
    [TestFixture]
    public class IsAnyOf_Tests
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
        {
            Action TestMethod = () => { throw new ArgumentNullException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
                    typeof(FormatException),
                    typeof(KeyNotFoundException)))
                {
                    // Handle expected Exceptions
                    return;
                }

                //else throw original
                throw;
            }
        }

        [Test, ExpectedException(typeof(OutOfMemoryException))]
        public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
        {
            Action TestMethod = () => { throw new OutOfMemoryException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(OutOfMemoryException),
                    typeof(StackOverflowException)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}
HodlDwon
sumber
1
Meningkatkan bahasa bukan "lebih elegan". Di banyak tempat ini sebenarnya menciptakan neraka pemeliharaan. Bertahun-tahun kemudian, banyak programmer tidak bangga dengan monster apa yang mereka buat. Bukan apa yang biasa Anda baca. Ini dapat menyebabkan "ya?" efek, atau bahkan "WTF" yang parah. Kadang membingungkan. Satu-satunya hal yang dilakukannya adalah membuat kode lebih sulit untuk dipahami oleh mereka yang perlu menanganinya dalam pemeliharaan - hanya karena seorang programmer mencoba menjadi "pintar". Selama bertahun-tahun, saya belajar bahwa solusi "pintar" itu jarang juga merupakan solusi yang baik.
Kaii
1
atau dalam beberapa kata: tetap dengan kemungkinan bahasa yang disediakan secara asli. jangan mencoba menimpa semantik bahasa, hanya karena Anda tidak menyukainya. Kolega Anda (dan mungkin saya yang akan datang) akan berterima kasih, jujur.
Kaii
Perhatikan juga, solusi Anda hanya mendekati semantik C # 6 when, seperti versi apa puncatch (Exception ex) {if (...) {/*handle*/} throw;} . Nilai sebenarnya dari whenadalah bahwa filter berjalan sebelum pengecualian ditangkap , sehingga menghindari korupsi pengeluaran / tumpukan melempar kembali. Ini mengambil keuntungan dari fitur CLR yang sebelumnya hanya dapat diakses oleh VB dan MSIL.
Marc L.
Lebih elegan? Contoh ini sangat besar untuk masalah yang begitu sederhana dan kodenya begitu mengerikan sehingga bahkan tidak layak untuk dilihat. Tolong jangan membuat kode ini masalah orang lain pada proyek yang sebenarnya.
KthProg
seluruh IsAnyOfmetode Anda dapat ditulis ulang hanya sebagaip_comparisons.Contains(p_parameter)
maksymiuk
7

Karena saya merasa jawaban-jawaban ini hanya menyentuh permukaan, saya berusaha menggali lebih dalam.

Jadi yang ingin kita lakukan adalah sesuatu yang tidak dapat dikompilasi, katakan:

// Won't compile... damn
public static void Main()
{
    try
    {
        throw new ArgumentOutOfRangeException();
    }
    catch (ArgumentOutOfRangeException)
    catch (IndexOutOfRangeException) 
    {
        // ... handle
    }

Alasan kami menginginkan ini adalah karena kami tidak ingin penangan pengecualian menangkap hal-hal yang kami butuhkan nanti dalam proses. Tentu, kita dapat menangkap Pengecualian dan memeriksa dengan 'jika' apa yang harus dilakukan, tapi jujur ​​saja, kita tidak benar-benar menginginkan itu. (FxCop, masalah debugger, keburukan)

Jadi mengapa kode ini tidak dapat dikompilasi - dan bagaimana kita bisa meretasnya sedemikian rupa sehingga akan terjadi?

Jika kita melihat kodenya, yang ingin kita lakukan adalah meneruskan panggilan. Namun, menurut MS Partition II, blok pengendali pengecualian IL tidak akan berfungsi seperti ini, yang dalam hal ini masuk akal karena itu akan menyiratkan bahwa objek 'pengecualian' dapat memiliki tipe yang berbeda.

Atau untuk menuliskannya dalam kode, kami meminta kompiler untuk melakukan sesuatu seperti ini (yah itu tidak sepenuhnya benar, tapi itu adalah hal yang paling dekat yang saya kira):

// Won't compile... damn
try
{
    throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
    goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
    Console.WriteLine("Handle!");
}

Alasan bahwa ini tidak akan dikompilasi cukup jelas: apa jenis dan nilai yang akan dimiliki objek '$ exception' (yang ada di sini disimpan dalam variabel 'e')? Cara kami ingin kompiler menangani ini adalah dengan mencatat bahwa tipe dasar umum dari kedua pengecualian adalah 'Pengecualian', menggunakannya untuk variabel yang berisi kedua pengecualian, dan kemudian hanya menangani dua pengecualian yang ditangkap. Cara ini diterapkan di IL adalah sebagai 'filter', yang tersedia di VB.Net.

Untuk membuatnya bekerja dalam C #, kita perlu variabel sementara dengan tipe dasar 'Pengecualian' yang benar. Untuk mengontrol aliran kode, kita dapat menambahkan beberapa cabang. Ini dia:

    Exception ex;
    try
    {
        throw new ArgumentException(); // for demo purposes; won't be caught.
        goto noCatch;
    }
    catch (ArgumentOutOfRangeException e) {
        ex = e;
    }
    catch (IndexOutOfRangeException e) {
        ex = e;
    }

    Console.WriteLine("Handle the exception 'ex' here :-)");
    // throw ex ?

noCatch:
    Console.WriteLine("We're done with the exception handling.");

Kerugian yang jelas untuk ini adalah bahwa kita tidak dapat melempar ulang dengan benar, dan - yah jujur ​​saja - bahwa itu solusi yang jelek. Keburukan dapat diperbaiki sedikit dengan melakukan eliminasi cabang, yang membuat solusinya sedikit lebih baik:

Exception ex = null;
try
{
    throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
    ex = e;
}
catch (IndexOutOfRangeException e)
{
    ex = e;
}
if (ex != null)
{
    Console.WriteLine("Handle the exception here :-)");
}

Tinggal menyisakan 'lemparan ulang'. Agar ini berhasil, kita harus dapat melakukan penanganan di dalam blok 'tangkap' - dan satu-satunya cara untuk membuat pekerjaan ini adalah dengan menangkap objek 'Pengecualian'.

Pada titik ini, kita dapat menambahkan fungsi terpisah yang menangani berbagai jenis Pengecualian menggunakan resolusi kelebihan beban, atau untuk menangani Pengecualian. Keduanya memiliki kekurangan. Untuk memulai, inilah cara melakukannya dengan fungsi pembantu:

private static bool Handle(Exception e)
{
    Console.WriteLine("Handle the exception here :-)");
    return true; // false will re-throw;
}

public static void Main()
{
    try
    {
        throw new OutOfMemoryException();
    }
    catch (ArgumentException e)
    {
        if (!Handle(e)) { throw; }
    }
    catch (IndexOutOfRangeException e)
    {
        if (!Handle(e)) { throw; }
    }

    Console.WriteLine("We're done with the exception handling.");

Dan solusi lainnya adalah menangkap objek Exception dan menanganinya sesuai. Terjemahan paling literal untuk ini, berdasarkan konteks di atas adalah ini:

try
{
    throw new ArgumentException();
}
catch (Exception e)
{
    Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
    if (ex != null)
    {
        Console.WriteLine("Handle the exception here :-)");
        // throw ?
    }
    else 
    {
        throw;
    }
}

Jadi untuk menyimpulkan:

  • Jika kami tidak ingin melempar ulang, kami mungkin mempertimbangkan untuk menangkap pengecualian yang benar, dan menyimpannya untuk sementara waktu.
  • Jika handler sederhana, dan kami ingin menggunakan kembali kode, solusi terbaik mungkin adalah memperkenalkan fungsi pembantu.
  • Jika kita ingin melempar ulang, kita tidak punya pilihan selain memasukkan kode ke dalam catch handler 'Pengecualian', yang akan memecah FxCop dan pengecualian debugger yang tidak tertangkap.
atlaste
sumber
7

Ini adalah masalah klasik yang dihadapi setiap pengembang C # pada akhirnya.

Biarkan saya membagi pertanyaan Anda menjadi 2 pertanyaan. Pertama,

Bisakah saya menangkap beberapa pengecualian sekaligus?

Singkatnya, tidak.

Yang mengarah pada pertanyaan selanjutnya,

Bagaimana cara saya menghindari menulis kode duplikat mengingat saya tidak bisa menangkap beberapa tipe pengecualian di blok catch () yang sama?

Mengingat sampel spesifik Anda, di mana nilai kembali lebih murah untuk dibangun, saya suka mengikuti langkah-langkah ini:

  1. Inisialisasi WebId ke nilai mundur.
  2. Bangun Guid baru dalam variabel sementara.
  3. Setel WebId ke variabel sementara yang dibangun sepenuhnya. Jadikan ini pernyataan akhir dari blok coba {}.

Jadi kodenya terlihat seperti:

try
{
    WebId = Guid.Empty;
    Guid newGuid = new Guid(queryString["web"]);
    // More initialization code goes here like 
    // newGuid.x = y;
    WebId = newGuid;
}
catch (FormatException) {}
catch (OverflowException) {}

Jika ada pengecualian yang dilemparkan, maka WebID tidak pernah diatur ke nilai setengah dibangun, dan tetap Guid.Empty.

Jika membangun kembali nilai itu mahal, dan mengatur ulang nilai jauh lebih murah, maka saya akan memindahkan kode reset ke fungsinya sendiri:

try
{
    WebId = new Guid(queryString["web"]);
    // More initialization code goes here.
}
catch (FormatException) {
    Reset(WebId);
}
catch (OverflowException) {
    Reset(WebId);
}
Jeffrey Rennie
sumber
Ini bagus, "pengkodean ekologis" yaitu Anda berpikir ke depan tentang kode & jejak data Anda dan memastikan tidak ada kebocoran nilai yang diproses setengah. Bagus akan mengikuti pola ini, terima kasih Jeffrey!
Tahir Khalid
6

Jadi, Anda mengulang banyak kode dalam setiap pengecualian-switch? Kedengarannya seperti mengekstraksi metode akan menjadi ide dewa, bukan?

Jadi kode Anda sampai seperti ini:

MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }

void Reset(MyClass instance) { /* reset the state of the instance */ }

Saya heran mengapa tidak ada yang memperhatikan duplikasi kode itu.

Dari C # 6 Anda selanjutnya memiliki filter pengecualian seperti yang telah disebutkan oleh orang lain. Jadi, Anda dapat mengubah kode di atas menjadi ini:

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}
HimBromBeere
sumber
3
"Aku ingin tahu mengapa tidak ada yang memperhatikan duplikasi kode itu." - uh, apa? The Seluruh titik pertanyaan adalah untuk menghilangkan kode duplikasi.
Mark Amery
4

Ingin menambahkan jawaban singkat saya ke utas yang sudah lama ini. Sesuatu yang belum disebutkan adalah urutan prioritas pernyataan penangkapan, lebih khusus Anda perlu mengetahui ruang lingkup setiap jenis pengecualian yang Anda coba tangkap.

Sebagai contoh jika Anda menggunakan pengecualian "tangkap semua" sebagai Pengecualian, itu akan mendahului semua pernyataan tangkap lainnya dan Anda jelas akan mendapatkan kesalahan penyusun namun jika Anda membalik urutan, Anda dapat menyusun pernyataan tangkap Anda (sedikit anti-pola yang saya pikir ) Anda dapat meletakkan catch-all type Exception di bagian bawah dan ini akan menangkap setiap pengecualian yang tidak sesuai dengan yang lebih tinggi dalam percobaan Anda .. blok tangkapan:

            try
            {
                // do some work here
            }
            catch (WebException ex)
            {
                // catch a web excpetion
            }
            catch (ArgumentException ex)
            {
                // do some stuff
            }
            catch (Exception ex)
            {
                // you should really surface your errors but this is for example only
                throw new Exception("An error occurred: " + ex.Message);
            }

Saya sangat merekomendasikan orang-orang meninjau dokumen MSDN ini:

Hirarki Pengecualian

Tahir Khalid
sumber
4

Mungkin mencoba untuk menjaga kode Anda sederhana seperti menempatkan kode umum dalam suatu metode, seperti yang akan Anda lakukan di bagian lain dari kode yang tidak ada dalam klausa catch?

Misalnya:

try
{
    // ...
}
catch (FormatException)
{
    DoSomething();
}
catch (OverflowException)
{
    DoSomething();
}

// ...

private void DoSomething()
{
    // ...
}

Betapa saya akan melakukannya, berusaha menemukan yang sederhana adalah pola yang indah

Żubrówka
sumber
3

Perhatikan bahwa saya memang menemukan satu cara untuk melakukannya, tetapi ini lebih mirip bahan untuk The Daily WTF :

catch (Exception ex)
{
    switch (ex.GetType().Name)
    {
        case "System.FormatException":
        case "System.OverflowException":
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}
Michael Stum
sumber
9
-1 suara, +5 WTF :-) Ini seharusnya tidak ditandai sebagai jawaban, tapi itu kelewat batas.
Aaron
1
Tidak masalah seberapa sederhananya kita bisa melakukannya. Tapi dia tidak duduk diam dan muncul dengan pandangannya untuk menyelesaikannya. Sangat menghargai.
Maxymus
2
Namun, jangan benar-benar melakukan ini, gunakan Filter Pengecualian di C # 6 atau jawaban lainnya - Saya menempatkan ini di sini secara khusus sebagai "Ini adalah satu cara, tetapi ini buruk dan saya ingin melakukan sesuatu yang lebih baik".
Michael Stum
MENGAPA ini buruk? Saya bingung Anda tidak bisa menggunakan pengecualian dalam pernyataan switch secara langsung.
MKesper
3
@ Markes, saya melihat beberapa alasan itu buruk. Ini membutuhkan penulisan nama kelas yang sepenuhnya memenuhi syarat sebagai string literal, yang rentan terhadap kesalahan ketik yang tidak dapat menyelamatkan Anda dari kompiler. (Ini penting karena di banyak toko kasus kesalahan kurang diuji dengan baik dan kesalahan sepele di dalamnya lebih mungkin untuk dilewatkan.) Ini juga akan gagal untuk mencocokkan pengecualian yang merupakan subkelas dari salah satu kasus yang ditentukan. Dan, karena menjadi string, kasing akan dilewatkan oleh alat seperti VS "Temukan Semua Referensi" - yang relevan jika Anda ingin menambahkan langkah pembersihan di mana-mana pengecualian khusus ditangkap.
Mark Amery
2

Perlu disebutkan di sini. Anda dapat menanggapi beberapa kombinasi (Kesalahan pengecualian dan pengecualian. Pesan).

Saya mengalami skenario penggunaan kasus ketika mencoba untuk melemparkan objek kontrol dalam datagrid, dengan konten sebagai TextBox, TextBlock atau CheckBox. Dalam hal ini Pengecualian yang dikembalikan adalah sama, tetapi pesannya beragam.

try
{
 //do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
} 
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
} 
George
sumber
0

Saya ingin menyarankan jawaban terpendek (satu lagi gaya fungsional ):

        Catch<FormatException, OverflowException>(() =>
            {
                WebId = new Guid(queryString["web"]);
            },
            exception =>
            {
                WebId = Guid.Empty;
            });

Untuk ini, Anda perlu membuat beberapa metode "Catch" overload, mirip dengan System.Action:

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
    }

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2, TException3>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
    }

dan seterusnya sebanyak yang Anda inginkan. Tetapi Anda perlu melakukannya sekali dan Anda dapat menggunakannya di semua proyek Anda (atau, jika Anda membuat paket nuget, kami juga bisa menggunakannya).

Dan implementasi CatchMany:

    [DebuggerNonUserCode]
    public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
        params Type[] exceptionTypes)
    {
        try
        {
            tryBlock();
        }
        catch (Exception exception)
        {
            if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
            else throw;
        }
    }

ps Saya belum menaruh cek nol untuk kesederhanaan kode, pertimbangkan untuk menambahkan validasi parameter.

ps2 Jika Anda ingin mengembalikan nilai dari tangkapan, perlu melakukan metode Penangkapan yang sama, tetapi dengan pengembalian dan Fungsi alih-alih Tindakan dalam parameter.

Eugene Gorbovoy
sumber
-15

Panggil saja coba dan tangkap dua kali.

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
try
{
    WebId = new Guid(queryString["web"]);
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

Sederhana saja !!

pria terpelajar
sumber
3
um ini mengalahkan tujuan pertanyaan. Dia menanyakan pertanyaan ini untuk menghilangkan kode duplikat. jawaban ini menambahkan lebih banyak kode rangkap.
James Esh
-23

Dalam c # 6.0, Filter Pengecualian adalah peningkatan untuk penanganan pengecualian

try
{
    DoSomeHttpRequest();
}
catch (System.Web.HttpException e)
{
    switch (e.GetHttpCode())
    {
        case 400:
            WriteLine("Bad Request");
        case 500:
            WriteLine("Internal Server Error");
        default:
            WriteLine("Generic Error");
    }
}
Kashif
sumber
13
Contoh ini tidak menunjukkan penggunaan filter pengecualian.
user247702
Ini adalah cara standar untuk memfilter pengecualian di c # 6.0
Kashif
5
Lihatlah lagi apa sebenarnya filter pengecualian itu. Anda tidak menggunakan filter pengecualian dalam contoh Anda. Ada contoh yang tepat dalam jawaban ini yang diposting setahun sebelum Anda.
user247702
6
Contoh pengecualian penyaringan akancatch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
saluce