Złapać wiele wyjątków jednocześnie?

2140

Odradza się po prostu łapanie System.Exception. Zamiast tego należy wychwytywać tylko „znane” wyjątki.

To czasami prowadzi do niepotrzebnego powtarzającego się kodu, na przykład:

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

Zastanawiam się: czy istnieje sposób, aby złapać oba wyjątki i zadzwonić tylko WebId = Guid.Emptyraz?

Podany przykład jest raczej prosty, ponieważ jest to tylko GUID. Ale wyobraź sobie kod, w którym wielokrotnie modyfikujesz obiekt, a jeśli jedna z manipulacji zakończy się w oczekiwany sposób, chcesz „zresetować” object. Jeśli jednak wystąpi nieoczekiwany wyjątek, nadal chcę go podnieść.

Michael Stum
źródło
5
Jeśli używasz .net 4 i nowszych, wolę używać agregateexception msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
Bepenfriends
2
Bepenfriends- Od System.Guid nie rzucać AggregateException , byłoby wspaniale, gdyby można (lub ktoś) pisać odpowiedzi pokazujący jak można owinąć go w AggregateException etc ..
jazu
1
Przy użyciu AggregateException:
Zgłaszanie wyjątku
11
„Nie zaleca się po prostu przechwytywania System.Exception”. -I jeśli metoda może zgłosić 32 rodzaje wyjątków, co robi? napisać haczyk dla każdego z nich osobno?
giorgim
5
Jeśli metoda zgłasza 32 różne rodzaje wyjątków, jest źle napisana. Albo nie wychwytuje wyjątków, które wywołują własne wywołania, robi za dużo FAR w jednej metodzie, albo większość / wszystkie te 32 powinny być pojedynczym wyjątkiem z kodem przyczyny.
Flynn1179

Odpowiedzi:

2100

Złap System.Exceptioni włącz typy

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

    throw;
}
Joseph Daigle
źródło
69
Niestety FxCop (tj. - Visual Studio Code Analysis) nie lubi, gdy wychwytujesz wyjątek.
Andrew Garrison
15
Zgadzam się z nie wyłapywaniem wyjątku, ale w tym przypadku haczyk jest filtrem. Możesz mieć warstwę wyższą, która będzie obsługiwać inne typy wyjątków. Powiedziałbym, że jest to poprawne, nawet jeśli zawiera haczyk (wyjątek x). Nie modyfikuje przepływu programu, po prostu obsługuje pewne wyjątki, a następnie pozwala pozostałej części aplikacji radzić sobie z innymi typami wyjątków.
kg
28
Najnowsza wersja FxCop nie zgłasza wyjątku, gdy używany jest powyższy kod.
Peter
28
Nie jestem pewien, co było nie tak z kodem OP. Zaakceptowana odpowiedź nr 1 to prawie dwa razy więcej wierszy i znacznie mniej czytelna.
João Bragança
22
@ JoãoBragança: Podczas gdy w tej odpowiedzi w tym przykładzie użyto więcej wierszy, spróbuj sobie wyobrazić, jeśli masz do czynienia z na przykład plikiem IO, a wszystko, co chcesz zrobić, to wychwycić te wyjątki i przesłać logi, ale tylko te, których oczekujesz od swojego metody plików IO. Wtedy często masz do czynienia z większą liczbą (około 5 lub więcej) różnych rodzajów wyjątków. W takiej sytuacji takie podejście może zaoszczędzić ci niektórych linii.
Xilconic,
594

EDYCJA: Zgadzam się z innymi, którzy twierdzą, że od wersji C # 6.0 filtry wyjątków są teraz całkiem dobrym sposobem:catch (Exception ex) when (ex is ... || ex is ... )

Tyle że nadal nienawidzę układu z jedną linią i osobiście ułożyłem kod w następujący sposób. Myślę, że jest to tak funkcjonalne, jak estetyczne, ponieważ uważam, że poprawia zrozumienie. Niektórzy mogą się nie zgadzać:

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

ORYGINALNY:

Wiem, że jestem trochę spóźniony na przyjęcie tutaj, ale święty dym ...

Przechodząc prosto do sedna, ten rodzaj powiela wcześniejszą odpowiedź, ale jeśli naprawdę chcesz wykonać wspólną akcję dla kilku typów wyjątków i utrzymać całość w porządku w ramach jednej metody, dlaczego po prostu nie użyć lambda / closure / inline funkcja zrobić coś takiego: Mam na myśli całkiem spore szanse, że zdasz sobie sprawę, że po prostu chcesz, aby to zamknięcie było odrębną metodą, z której możesz korzystać wszędzie. Ale wtedy będzie to bardzo łatwe, bez faktycznej zmiany reszty kodu strukturalnie. Dobrze?

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 ); }
}

Nie mogę przestać się zastanawiać ( ostrzeżenie: trochę ironii / sarkazmu przed nami), dlaczego, do licha, podejmujesz te wszystkie wysiłki, aby po prostu zastąpić następujące:

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

... z pewną szaloną odmianą tego następnego zapachu kodu, mam na myśli tylko po to, by udawać, że oszczędzasz kilka naciśnięć klawiszy.

// 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;
}

Ponieważ z pewnością nie jest automatycznie bardziej czytelny.

To prawda, zostawiłem trzy identyczne przypadki z /* write to a log, whatever... */ return;pierwszego przykładu.

Ale o to mi chodzi. Wszyscy słyszeliście o funkcjach / metodach, prawda? Poważnie. Napisz wspólną ErrorHandlerfunkcję i wywołaj ją z każdego bloku catch.

Jeśli zapytasz mnie, drugi przykład (ze słowami kluczowymi ifi is) jest zarówno znacznie mniej czytelny, a jednocześnie znacznie bardziej podatny na błędy podczas fazy konserwacji twojego projektu.

Faza konserwacji, dla każdego, kto może być stosunkowo nowy w programowaniu, będzie stanowić 98,7% lub więcej całkowitego czasu życia twojego projektu, a biedny schmuck wykonujący konserwację prawie na pewno będzie kimś innym niż ty. I jest bardzo duża szansa, że ​​spędzą 50% swojego czasu na pracy przeklinając twoje imię.

I oczywiście FxCop szczeka na ciebie, więc musisz również dodać atrybut do swojego kodu, który ma dokładnie zip związany z działającym programem, i jest tylko po to, aby powiedzieć FxCop, aby zignorował problem, który w 99,9% przypadków jest całkowicie poprawne w oznaczaniu. I przepraszam, mogę się mylić, ale czy ten atrybut „ignoruj” nie jest tak naprawdę wkompilowany w twoją aplikację?

Czy umieszczenie całego iftestu w jednym wierszu uczyniłoby go bardziej czytelnym? Nie wydaje mi się To znaczy, już dawno temu inny programista gwałtownie argumentował, że umieszczenie większej ilości kodu w jednym wierszu sprawiłoby, że „działałby on szybciej”. Ale oczywiście był niesamowitym wariatem. Próba wyjaśnienia mu (z prostą twarzą - co było wyzwaniem), w jaki sposób interpreter lub kompilator podzieliłby tę długą linię na dyskretne instrukcje składające się z jednej instrukcji na linię - zasadniczo identyczny z wynikiem, gdyby poszedł naprzód i po prostu sprawił, że kod był czytelny, zamiast próbować sprytnie skompilować kompilator - nie miał na niego żadnego wpływu. Ale dygresję.

O ile mniej jest to czytelne, gdy dodasz trzy kolejne typy wyjątków, za miesiąc lub dwa od teraz? (Odpowiedź: robi się o wiele mniej czytelny).

Jednym z głównych punktów jest to, że większość formatowania tekstowego kodu źródłowego, na który wszyscy patrzymy każdego dnia, polega na tym, aby uświadomić innym ludziom, co tak naprawdę dzieje się podczas działania kodu. Ponieważ kompilator zamienia kod źródłowy w coś zupełnie innego i nie obchodzi go styl formatowania kodu. Więc też wszystko w jednej linii jest do kitu.

Tylko mówię...

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}
Craig
źródło
36
Kiedy po raz pierwszy natknąłem się na to pytanie, znalazłem całą akceptowaną odpowiedź. Fajnie, mogę złapać wszystkie Exceptioni sprawdzić typ. Myślałem, że to wyczyściło kod, ale coś sprawiło, że wróciłem do pytania i właściwie przeczytałem inne odpowiedzi na pytanie. Żułam to przez chwilę, ale muszę się z tobą zgodzić. Jest to bardziej czytelne i utrzymaniu użyć funkcji wysychać kodu niż złapać wszystko, sprawdzić typ porównując z listą, kod owijania i rzucania. Dziękujemy za spóźnienie i zapewnienie alternatywnej i rozsądnej opcji (IMO). +1.
błąd
8
Korzystanie z funkcji obsługi błędów nie działałoby, gdybyś chciał dołączyć throw;. Trzeba będzie powtarzać ten wiersz kodu w każdym bloku catch (oczywiście nie koniec świata, ale warto o tym wspomnieć, ponieważ to kod należy powtórzyć).
kad81
5
@ kad81, to prawda, ale nadal będziesz mieć korzyść z pisania kodu rejestrującego i czyszczącego w jednym miejscu i zmieniania go w jednym miejscu, jeśli to konieczne, bez głupiej semantyki przechwytywania podstawowego typu wyjątku, a następnie rozgałęziania w oparciu o typ wyjątku. I ta jedna dodatkowa throw();instrukcja w każdym bloku catch to niewielka cena do zapłacenia, IMO, i nadal pozostawia Ci możliwość dodatkowego czyszczenia specyficznego dla typu wyjątku, jeśli to konieczne.
Craig
2
Cześć @Reitffunk, wystarczy użyć Func<Exception, MyEnumType>zamiast Action<Exception>. To znaczy Func<T, Result>, Resultże jest to typ zwrotu.
Craig
3
Zgadzam się tutaj całkowicie. Ja też przeczytałem pierwszą odpowiedź i myślę wydaje się logiczna. Przeniesiono do ogólnej 1 dla wszystkich procedur obsługi wyjątków. Coś we mnie zmusiło mnie do wewnętrznego wymiotowania ... więc cofnąłem kod. Potem natknąłem się na to piękno! To musi być zaakceptowana odpowiedź
Conor Gallagher
372

Jak zauważyli inni, ifw bloku catch możesz umieścić instrukcję, aby ustalić, co się dzieje. C # 6 obsługuje filtry wyjątków, więc następujące funkcje będą działać:

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

MyFilterMetoda może wtedy wyglądać następująco:

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

Alternatywnie można to wszystko zrobić w wierszu (prawa strona instrukcji when musi być tylko wyrażeniem logicznym).

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

Różni się to od używania ifinstrukcji z catchbloku, użycie filtrów wyjątków nie rozwinie stosu.

Możesz pobrać Visual Studio 2015, aby to sprawdzić.

Jeśli chcesz kontynuować korzystanie z programu Visual Studio 2013, możesz zainstalować następujący pakiet nuget:

Zainstaluj pakiet Microsoft.Net.Compilers

W momencie pisania tego tekstu będzie to obejmować obsługę C # 6.

Odwołanie do tego pakietu spowoduje zbudowanie projektu przy użyciu określonej wersji kompilatorów C # i Visual Basic zawartych w pakiecie, w przeciwieństwie do dowolnej wersji zainstalowanej w systemie.

Joe
źródło
3
Cierpliwie czekam na oficjalne wydanie 6 ... Chciałbym zobaczyć, jak to się dostanie, kiedy to się stanie.
RubberDuck,
@ RubberDuck Umieram za operatora propagacji zerowej z C # 6. Próbuję przekonać resztę mojego zespołu, że ryzyko niestabilnego języka / kompilatora jest tego warte. Wiele drobnych ulepszeń o ogromnym wpływie. Jeśli chodzi o oznaczenie jako odpowiedzi, nie jest to ważne, dopóki ludzie zdają sobie sprawę, że taka wola / jest możliwa, jestem szczęśliwy.
Joe
Dobrze?! W najbliższej przyszłości przyjrzę się mojej bazie kodu. =) Wiem, że sprawdzenie nie jest ważne, ale biorąc pod uwagę, że zaakceptowana odpowiedź wkrótce stanie się nieaktualna, mam nadzieję, że OP wróci, aby to sprawdzić, aby zapewnić odpowiednią widoczność.
RubberDuck,
Po części dlatego nie przyznałem tego jeszcze @Joe. Chcę, żeby to było widoczne. Możesz jednak dodać przykład filtra wbudowanego, aby uzyskać większą przejrzystość.
RubberDuck,
188

Niestety nie w C #, ponieważ potrzebujesz do tego filtru wyjątków, a C # nie ujawnia tej funkcji MSIL. VB.NET ma jednak taką możliwość, np

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

Możesz użyć anonimowej funkcji do enkapsulacji kodu błędu, a następnie wywołać go w tych konkretnych blokach catch:

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}
Greg Beech
źródło
26
Ciekawy pomysł i kolejny przykład, że VB.net ma czasem kilka interesujących zalet w stosunku do C #
Michael Stum
47
@MichaelStum z tego rodzaju składni bym prawie nazywają to ciekawe w ogóle ... dreszcz
MarioDS
17
Filtry wyjątków pojawią się w c # 6! Zwróć uwagę na różnicę w stosowaniu filtrów na rzecz ponownego uruchomienia roslyn.codeplex.com/discussions/541301
Arne Deruwe
@ArneDeruwe Dziękujemy za ten link! Właśnie nauczyłem się jeszcze jednego ważnego powodu, aby nie rzucać ponownie: throw e;niszczy stacktrace i callstack, throw;niszczy „tylko” callstack (czyniąc zrzuty awarii bezużytecznymi!) Bardzo dobry powód, aby tego nie używać, jeśli można tego uniknąć!
AnorZaken,
1
Od C # 6 dostępne są filtry wyjątków! Wreszcie.
Danny
134

Ze względu na kompletność, od .NET 4.0 kod można przepisać jako:

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

TryParse nigdy nie zgłasza wyjątków i zwraca false, jeśli format jest niepoprawny, ustawiając WebId na Guid.Empty.


Od wersji C # 7 można uniknąć wprowadzania zmiennej w osobnym wierszu:

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

Możesz także utworzyć metody analizowania zwracanych krotek, które nie są jeszcze dostępne w .NET Framework od wersji 4.6:

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

I użyj ich w ten sposób:

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

Następna bezużyteczna aktualizacja tej bezużytecznej odpowiedzi pojawia się, gdy dekonstrukcja parametrów wyjściowych jest zaimplementowana w C # 12. :)

Athari
źródło
19
Dokładnie - zwięźle, a ty całkowicie omijasz utratę wydajności związaną z obsługą wyjątku, złą formę celowego wykorzystywania wyjątków do kontrolowania przebiegu programu i nieostrość skupienia się na logice konwersji, trochę tutaj i trochę tam .
Craig
9
Wiem, co miałeś na myśli, ale oczywiście Guid.TryParsenigdy nie wraca Guid.Empty. Jeśli łańcuch ma niepoprawny format, ustawia resultparametr wyjściowy na Guid.Empty, ale zwraca false . Wspominam o tym, ponieważ widziałem kod, który robi rzeczy w stylu Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }, co zwykle jest złe, jeśli smogłoby to być ciąg znaków Guid.Empty.
14
wow, odpowiedziałeś na pytanie, tyle że nie jest to zgodne z duchem pytania. Większy problem to coś innego :(
nawfal
6
Właściwy wzorzec używania TryParse jest oczywiście bardziej podobny if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ }, co nie pozostawia dwuznaczności, jak zepsuty przykład, w którym wartością wejściową może być reprezentacja ciągu Guid.
Craig
2
Ta odpowiedź może rzeczywiście być poprawna w odniesieniu do Guid.Parse, ale przeoczyła cały punkt pierwotnego pytania. Co nie miało nic wspólnego z Guid.Parse, ale dotyczyło wychwytywania wyjątku vs. FormatException / OverflowException / itp.
Conor Gallagher,
114

Filtry wyjątków są teraz dostępne w wersji c # 6+. Możesz to zrobić

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

W wersji C # 7.0+ można to również połączyć z dopasowaniem wzorca

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
źródło
Ta metoda jest preferowana nie tylko dlatego, że jest prosta i przejrzysta, ale także nie musi cofać stosu, jeśli warunki nie są spełnione, co zapewnia lepszą wydajność i informacje diagnostyczne w porównaniu z powtórzeniem.
Joe
74

Jeśli możesz zaktualizować aplikację do C # 6, masz szczęście. Nowa wersja C # ma zaimplementowane filtry wyjątków. Możesz więc napisać to:

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

Niektórzy uważają, że ten kod jest taki sam jak

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

Ale nie jest. W rzeczywistości jest to jedyna nowa funkcja w języku C # 6, której nie można emulować we wcześniejszych wersjach. Po pierwsze, powtórzenie rzutu oznacza więcej narzutu niż pominięcie haczyka. Po drugie, nie jest semantycznie równoważny. Nowa funkcja zachowuje nienaruszony stos podczas debugowania kodu. Bez tej funkcji zrzut awaryjny jest mniej przydatny, a nawet bezużyteczny.

Zobacz dyskusję na ten temat w CodePlex . I przykład pokazujący różnicę .

Maniero
źródło
4
Rzut bez wyjątku zachowuje stos, ale „throw ex” go nadpisze.
Ivan
32

Jeśli nie chcesz używać ifoświadczenie obrębie catchzakresów, w C# 6.0można użyć Exception Filtersskładni , która została już obsługiwany przez CLR w wersji Zapowiedzi ale istniała tylko w VB.NET/ MSIL:

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

Ten kod złapie Exceptiontylko wtedy, gdy jest to InvalidDataExceptionlub ArgumentNullException.

Właściwie można umieścić w zasadzie dowolny warunek w tej whenklauzuli:

static int a = 8;

...

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

Zauważ, że w przeciwieństwie do ifinstrukcji wewnątrz catchzakresu, Exception Filtersnie można wyrzucić Exceptions, a kiedy to zrobią lub gdy warunek nie zostanie spełniony true, następny catchwarunek zostanie oceniony:

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");
}

Wyjście: ogólny połów.

Jeśli jest więcej niż jeden true Exception Filter- pierwszy zostanie zaakceptowany:

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");
}

Wyjście: połów.

I jak widać w MSILkodzie, kod nie jest tłumaczony na ifinstrukcje, ale na Filtersi Exceptionsnie można go wyrzucić z obszarów oznaczonych Filter 1i, Filter 2ale filtr rzucający Exceptionnie powiedzie się, również ostatnia wartość porównania wypchnięta na stos przed endfilterpoleceniem określi sukces / awarię filtra ( Catch 1 XOR Catch 2 wykona się odpowiednio):

Filtry wyjątków MSIL

Również konkretnie Guidma tę Guid.TryParsemetodę.

Tamir Vered
źródło
+1 za pokazywanie wielu, gdy filtry i wyjaśnienie, co się dzieje, gdy używanych jest wiele filtrów.
steven87vt
26

Dzięki C # 7 odpowiedź od Michaela Stuma może zostać ulepszona przy zachowaniu czytelności instrukcji switch:

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

I z C # 8 jako wyrażeniem przełącznika:

catch (Exception ex)
{
    WebId = ex switch
    {
        _ when ex is FormatException || ex is OverflowException => Guid.Empty,
        _ => throw ex
    };
}
fabiański
źródło
3
To powinna być zaakceptowana odpowiedź od IMHO 2018.
MemphiZ,
6
Użycie odpowiedzi Mat J whenjest o wiele bardziej eleganckie / odpowiednie niż przełącznik.
rgoliveira
@rgoliveira: Zgadzam się, że w przypadku pytania zadanego w pytaniu odpowiedź Mat J jest bardziej elegancka i odpowiednia. Jednak trudno jest odczytać, jeśli masz inny kod, który chcesz wykonać, w zależności od typu wyjątku lub jeśli faktycznie chcesz użyć instancji wyjątku. Wszystkie te scenariusze można traktować jednakowo za pomocą tej instrukcji switch.
Fabian
1
@Fabian „jeśli masz inny kod, który chcesz wykonać w zależności od typu wyjątku lub jeśli rzeczywiście chcesz użyć instancji wyjątku”, to po prostu stwórz inny catchblok, lub zresztą musisz go rzucić .. Z mojego doświadczenia wynika , że throw;w twoim catchbloku jest prawdopodobnie zapach kodu.
rgoliveira
@rgoliveira: Użycie rzutu w bloku catch jest w porządku w kilku przypadkach patrz link . Ponieważ instrukcja case faktycznie korzysta z linku dopasowującego wzór , nie musisz rzutować, jeśli zastąpisz link operatora odrzucenia (podkreślenie) nazwą zmiennej. Nie zrozum mnie źle, zgadzam się z tobą, że filtry wyjątków są czystszym sposobem, ale wiele bloków catch dodaje wiele nawiasów klamrowych.
Fabian
20

Przyjęta odpowiedź wydaje się akceptowalna, z tym wyjątkiem, że CodeAnalysis / FxCop będzie narzekać na fakt, że wychwytuje ogólny typ wyjątku.

Wydaje się również, że operator „jest” może nieznacznie obniżyć wydajność.

CA1800: Nie przesyłaj niepotrzebnie poleceń „rozważ zamiast tego testowanie wyniku operatora„ jako ””, ale jeśli to zrobisz, będziesz pisać więcej kodu, niż gdyby każdy wyjątek był wychwytywany osobno.

Tak czy inaczej, oto co bym zrobił:

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}
Matt
źródło
19
Ale pamiętaj, że nie możesz powtórzyć wyjątku bez utraty śladu stosu, jeśli zrobisz to w ten sposób. (Zobacz komentarz Michaela Stuma do przyjętej odpowiedzi)
René,
2
Ten wzorzec można poprawić, przechowując wyjątek (proszę wybaczyć złe formatowanie - nie mogę wymyślić, jak wstawić kod w komentarzach): Wyjątek ex = null; spróbuj {// coś} złapać (FormatException e) {ex = e; } catch (OverflowException e) {ex = e; } if (ex! = null) {// coś innego i radzenie sobie z ex}
Jesse Weigert
3
@JesseWeigert: 1. Za pomocą kursorów tylnych można nadać fragmentowi tekstu czcionkę o jednolitym odstępie i jasnoszare tło. 2. Nadal nie będziesz w stanie powtórzyć oryginalnego wyjątku, w tym śledzenia stosu .
Oliver
2
@CleverNeologism, chociaż może być prawdą, że użycie isoperatora może mieć niewielki negatywny wpływ na wydajność, prawdą jest również, że procedura obsługi wyjątków nie jest miejscem, w którym należy się nadmiernie martwić optymalizacją wydajności. Jeśli Twoja aplikacja spędza tak dużo czasu w modułach obsługi wyjątków, że optymalizacja wydajności miałaby rzeczywistą różnicę w wydajności aplikacji, to są inne problemy z kodem, na które trzeba uważnie się przyjrzeć. Powiedziawszy to, nadal nie podoba mi się to rozwiązanie, ponieważ tracisz ślad stosu i ponieważ czyszczenie jest kontekstowo usuwane z instrukcji catch.
Craig
3
Jedynym momentem, w którym isoperator obniża wydajność, jest późniejsze wykonanie asoperacji (stąd niepotrzebnie kwalifikują regułę ). Jeśli wszystko, co robisz, to testowanie obsady bez potrzeby jej wykonywania, to isoperator jest dokładnie tym, czego chcesz użyć.
saluce
19

w C # 6 zalecanym podejściem jest użycie filtrów wyjątków, oto przykład:

 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
źródło
18

To jest wariant odpowiedzi Matta (wydaje mi się, że jest to trochę czystsze) ... użyj metody:

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

    WebId = Guid.Empty;
}

Wszelkie inne wyjątki zostaną zgłoszone, a kod WebId = Guid.Empty;nie zostanie trafiony. Jeśli nie chcesz, aby inne wyjątki powodowały awarię programu, po prostu dodaj to PO pozostałych dwóch chwytaniach:

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}
bsara
źródło
-1 Wykona się to WebId = Guid.Emtpyw przypadku, gdy nie zgłoszono wyjątku.
Sepster
4
@sepster Myślę, że sugerowane jest tutaj wyrażenie zwrotne po „// coś”. Nie podoba mi się to rozwiązanie, ale jest to konstruktywny wariant dyskusji. +1, aby cofnąć głosowanie :-)
toong
@Songster toong ma rację, założyłem, że jeśli chciałbyś tam wrócić, to postawiłbyś jeden ... Próbowałem udzielić mojej odpowiedzi na tyle ogólnej, aby dotyczyła wszystkich sytuacji, w przypadku gdy inni z podobnymi, ale niedokładnymi pytaniami skorzystaliby, ponieważ dobrze. Jednak dla pewności dodam returndo mojej odpowiedzi. Dzięki za wkład.
bsara
18

Odpowiedź Josepha Daigle'a jest dobrym rozwiązaniem, ale uważam, że następująca struktura jest nieco bardziej uporządkowana i mniej podatna na błędy.

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

    // Handle exception
}

Istnieje kilka zalet odwracania wyrażenia:

  • Instrukcja zwrotu nie jest konieczna
  • Kod nie jest zagnieżdżony
  • Nie ma ryzyka zapomnienia zwrotów „rzut” lub „powrót”, które w rozwiązaniu Józefa są oddzielone od wyrażenia.

Można go nawet spakować do pojedynczej linii (choć niezbyt ładnej)

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

    // Handle exception
}

Edit: filtrowanie wyjątek w C # 6.0 pozwoli składni czystsze bit i jest wyposażony w szereg innych korzyści na każdym obecnym rozwiązaniu. (przede wszystkim pozostawiając stos bez szwanku)

Oto jak ten sam problem wyglądałby przy użyciu składni C # 6.0:

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}
Stefan T.
źródło
2
+1, to najlepsza odpowiedź. Jest to lepsze niż najlepsza odpowiedź głównie dlatego, że nie ma return, chociaż odwrócenie warunku jest również nieco lepsze.
DCShannon,
Nawet o tym nie myślałem. Dobry połów, dodam go do listy.
Stefan T
16

@Micheal

Nieznacznie zmieniona wersja twojego kodu:

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

Porównywanie ciągów znaków jest brzydkie i wolne.

FlySwat
źródło
21
Dlaczego po prostu nie użyć słowa kluczowego „jest”?
Chris Pietschmann
29
@ Michael - Jeśli Microsoft wprowadził, powiedzmy, StringTooLongException pochodzący z FormatException, to nadal jest to wyjątek formatu, tylko określony. To zależy, czy chcesz semantykę „złap dokładnie ten typ wyjątku” czy „złap wyjątki, co oznacza, że ​​format ciągu był nieprawidłowy”.
Greg Beech,
6
@Michael - Zauważ też, że „catch (FormatException ex) ma drugą semantykę, będzie przechwytywać wszystko, co pochodzi od FormatException.
Greg Beech
14
@Alex No. „throw” bez „ex” przenosi oryginalny wyjątek, w tym ślad stosu oryginalnego, w górę. Dodanie „ex” powoduje zresetowanie śledzenia stosu, więc naprawdę otrzymujesz inny wyjątek niż oryginał. Jestem pewien, że ktoś inny może to wyjaśnić lepiej niż ja. :)
Samantha Branham
13
-1: Ten kod jest wyjątkowo delikatny - programista biblioteki może oczekiwać zastąpienia throw new FormatException();go throw new NewlyDerivedFromFormatException();bez łamania kodu przy użyciu biblioteki, i zachowa prawdę dla wszystkich przypadków obsługi wyjątków, z wyjątkiem przypadków, gdy ktoś używał ==zamiast is(lub po prostu catch (FormatException)).
Sam Harwell
13

Co powiesz na

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
Maurice
źródło
Działa to tylko wtedy, gdy kod połowu można w pełni przenieść do bloku próbnego. Ale kod obrazowania, w którym wykonujesz wiele manipulacji na obiekcie, a jeden w środku zawodzi i chcesz „zresetować” obiekt.
Michael Stum
4
W takim przypadku dodam funkcję resetowania i wywołam ją z wielu bloków catch.
Maurice
12
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}
Konstantin Spirin
źródło
11

Przestroga i ostrzeżenie: Kolejny miły, funkcjonalny styl.

To, co znajduje się w linku, nie odpowiada bezpośrednio na twoje pytanie, ale rozszerzenie go tak, aby wyglądało tak:

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

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

(Zasadniczo zapewnia kolejne puste Catchprzeciążenie, które zwraca się)

Ważniejsze pytanie brzmi: dlaczego . Nie sądzę, że koszt przeważa tutaj zysk :)

nawfal
źródło
1
Jedną możliwą zaletą tego podejścia jest to, że istnieje semantyczna różnica między wyłapywaniem a ponownym zgłaszaniem wyjątku w porównaniu z niezłapaniem go; w niektórych przypadkach kod powinien działać na podstawie wyjątku bez przechwytywania go. Jest to możliwe w vb.net, ale nie w C #, chyba że używa się opakowania napisanego w vb.net i wywołanego z C #.
supercat
1
Jak działa wyjątek bez wyłapywania go? Nie do końca cię rozumiem.
nawfal
@nawful ... przy użyciu filtru vb - funkcja filt (ex jako wyjątek): LogEx (ex): zwraca false ... następnie w linii catch: catch ex kiedy filt (ex)
FastAl
1
@ FastAl Czy nie jest to dozwolone przez filtry wyjątków w C # 6?
HimBromBeere
@HimBromBeere tak, są bezpośrednimi analogami
FastAl
9

Aktualizacja 2015-12-15: Zobacz https://stackoverflow.com/a/22864936/1718702 dla C # 6. Jest czystszy i teraz jest standardem w języku.

Przeznaczony dla osób, które chcą bardziej eleganckiego rozwiązania wychwyciło jeden raz i odfiltrowało wyjątki, używam metody rozszerzenia, jak pokazano poniżej.

Miałem już to rozszerzenie w mojej bibliotece, pierwotnie napisane do innych celów, ale działało idealnie idealnie do typesprawdzania wyjątków. Plus, imho, wygląda na czystsze niż kilka ||stwierdzeń. Ponadto, w przeciwieństwie do zaakceptowanej odpowiedzi, wolę jawne przetwarzanie wyjątków, więc ex is ...zachowywało się niepożądane zachowanie, ponieważ klasy pozbawione są przypisywane do typów nadrzędnych).

Stosowanie

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

Rozszerzenie IsAnyOf.cs (Zobacz przykład pełnej obsługi błędów dla zależności)

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;
        }
    }
}

Przykład pełnej obsługi błędów (kopiuj-wklej do nowej aplikacji konsoli)

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));
        }
    }
}

Dwa przykładowe testy jednostek NUnit

Zachowanie dopasowania dla Exceptiontypów jest dokładne (tzn. Dziecko NIE JEST dopasowaniem dla żadnego z jego typów rodzicielskich).

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
źródło
1
Ulepszanie języka nie jest „bardziej eleganckie”. W wielu miejscach stworzyło to piekło konserwacyjne. Wiele lat później wielu programistów nie jest dumnych z tego, jakiego potwora stworzyli. To nie jest to, co jesteś przyzwyczajony do czytania. Może powodować „huh?” efekt, a nawet poważne „WTF”. Czasami jest to mylące. Jedyne, co robi, to sprawia, że ​​kod jest trudniejszy do zrozumienia dla tych, którzy muszą sobie z nim poradzić w późniejszym czasie - tylko dlatego, że jeden programista próbował być „sprytny”. Z biegiem lat dowiedziałem się, że te „sprytne” rozwiązania rzadko też są dobre.
Kaii
1
lub w kilku słowach: trzymaj się możliwości, jakie zapewnia język ojczysty. nie próbuj zastępować semantyki języka tylko dlatego , że ich nie lubisz. Twoi koledzy (i ewentualnie przyszli ja) szczerze ci podziękują.
Kaii
Zauważ też, że twoje rozwiązanie tylko przybliża semantykę C # 6 when, tak jak każda wersja catch (Exception ex) {if (...) {/*handle*/} throw;}. Rzeczywistą wartością whenjest to, że filtr działa przed przechwyceniem wyjątku , dzięki czemu unika się uszkodzenia wydatków / stosu ponownego rzutu. Wykorzystuje funkcję CLR, która była wcześniej dostępna tylko dla VB i MSIL.
Marc L.
Bardziej elegancki? Ten przykład jest tak duży jak na tak prosty problem, a kod wygląda tak okropnie, że nawet nie warto było go oglądać. Nie rób z tego kodu problemu dla kogoś innego w rzeczywistym projekcie.
KthProg,
cała twoja IsAnyOfmetoda może być przepisana w prosty sposóbp_comparisons.Contains(p_parameter)
maksymiuk
7

Ponieważ czułem, że te odpowiedzi tylko dotknęły powierzchni, próbowałem kopać nieco głębiej.

Więc tak naprawdę chcielibyśmy zrobić coś, czego nie można skompilować, powiedz:

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

Powodem tego jest to, że nie chcemy, aby moduł obsługi wyjątków przechwytywał rzeczy, których potrzebujemy w dalszej części procesu. Jasne, możemy złapać wyjątek i sprawdzić „co”, co zrobić, ale bądźmy szczerzy, naprawdę tego nie chcemy. (FxCop, problemy z debuggerem, brzydota)

Dlaczego więc ten kod się nie skompiluje - i w jaki sposób możemy go zhakować w taki sposób, aby był?

Jeśli spojrzymy na kod, naprawdę chcielibyśmy przekazać połączenie. Jednak zgodnie z MS Partition II bloki obsługi wyjątków IL nie będą działać w ten sposób, co w tym przypadku ma sens, ponieważ oznaczałoby to, że obiekt „wyjątku” może mieć różne typy.

Lub, aby napisać to w kodzie, prosimy kompilator, aby zrobił coś takiego (cóż, nie jest to całkowicie poprawne, ale myślę, że jest to najbliższa możliwa rzecz):

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

Powód, dla którego nie zostanie skompilowany, jest dość oczywisty: jaki typ i wartość miałby obiekt „$ wyjątek” (które są tutaj przechowywane w zmiennych „e”)? Sposób, w jaki chcemy, aby kompilator sobie z tym poradził, to zwrócenie uwagi na to, że wspólnym typem podstawowym obu wyjątków jest „Wyjątek”, użyj tego, aby zmienna zawierała oba wyjątki, a następnie obsłuż tylko dwa wychwycone wyjątki. Sposób ten jest zaimplementowany w IL jako „filtr”, który jest dostępny w VB.Net.

Aby działało w języku C #, potrzebujemy zmiennej tymczasowej o poprawnym typie podstawowym „Wyjątek”. Aby kontrolować przepływ kodu, możemy dodać kilka gałęzi. Tutaj idzie:

    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.");

Oczywiste wady tego polegają na tym, że nie możemy rzucić ponownie, i - szczerze mówiąc - że jest to dość brzydkie rozwiązanie. Brzydotę można nieco naprawić, wykonując eliminację gałęzi, co sprawia, że ​​rozwiązanie jest nieco lepsze:

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 :-)");
}

To pozostawia tylko „powtórkę”. Aby to zadziałało, musimy być w stanie wykonać obsługę wewnątrz bloku „catch” - a jedynym sposobem na wykonanie tej pracy jest złapanie obiektu „Exception”.

W tym momencie możemy dodać osobną funkcję, która obsługuje różne typy wyjątków za pomocą rozdzielczości przeciążenia lub obsługi wyjątku. Oba mają wady. Aby rozpocząć, oto sposób na zrobienie tego z funkcją pomocnika:

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.");

Drugim rozwiązaniem jest przechwycenie obiektu wyjątku i odpowiednio go obsłużyć. Najbardziej dosłowne tłumaczenie tego, oparte na powyższym kontekście, jest następujące:

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;
    }
}

Podsumowując:

  • Jeśli nie chcemy ponownie rzucać, możemy rozważyć złapanie odpowiednich wyjątków i przechowywanie ich tymczasowo.
  • Jeśli program obsługi jest prosty i chcemy ponownie użyć kodu, najlepszym rozwiązaniem jest prawdopodobnie wprowadzenie funkcji pomocnika.
  • Jeśli chcemy przerzucić, nie mamy innego wyjścia, jak umieścić kod w module obsługi przechwytywania „Wyjątków”, który złamie FxCop i nieprzechwycone wyjątki twojego debuggera.
atlaste
źródło
7

Jest to klasyczny problem, z którym ostatecznie musi się zmierzyć każdy programista C #.

Pozwól, że podzielę twoje pytanie na 2 pytania. Pierwszy,

Czy mogę wyłapać wiele wyjątków jednocześnie?

Krótko mówiąc, nie.

Co prowadzi do następnego pytania

Jak uniknąć pisania zduplikowanego kodu, biorąc pod uwagę, że nie mogę złapać wielu typów wyjątków w tym samym bloku catch ()?

Biorąc pod uwagę konkretną próbkę, w której wartość rezerwowa jest tania w budowie, lubię wykonywać następujące kroki:

  1. Zainicjuj WebId do wartości rezerwowej.
  2. Zbuduj nowego Guida w zmiennej tymczasowej.
  3. Ustaw WebId na w pełni skonstruowaną zmienną tymczasową. Uczyń to końcową instrukcją bloku try {}.

Kod wygląda więc tak:

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) {}

Jeśli zostanie zgłoszony jakikolwiek wyjątek, WebId nigdy nie jest ustawiany na półkonstruowaną wartość i pozostaje Guid.Empty.

Jeśli konstruowanie wartości rezerwowej jest drogie, a resetowanie wartości jest znacznie tańsze, wówczas przestawiłbym kod resetowania na własną funkcję:

try
{
    WebId = new Guid(queryString["web"]);
    // More initialization code goes here.
}
catch (FormatException) {
    Reset(WebId);
}
catch (OverflowException) {
    Reset(WebId);
}
Jeffrey Rennie
źródło
To miłe, „ekologiczne kodowanie”, tzn. Myślisz z wyprzedzeniem o swoim śladzie kodu i danych i upewniasz się, że nie dojdzie do wycieku połowy przetworzonych wartości. Miło pójść za tym wzorem, dzięki Jeffrey!
Tahir Khalid
6

Więc powtarzasz dużo kodu w ramach każdego przełącznika wyjątków? Wygląda na to, że wyodrębnienie metody byłoby dobrym pomysłem, prawda?

Twój kod sprowadza się do tego:

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 */ }

Zastanawiam się, dlaczego nikt nie zauważył takiego powielania kodu.

Od C # 6 masz ponadto filtry wyjątków, o których wspominali już inni. Możesz więc zmodyfikować powyższy kod do tego:

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}
HimBromBeere
źródło
3
„Zastanawiam się, dlaczego nikt nie zauważył takiego powielania kodu”. - Oh, co? Cały punkt pytanie jest wyeliminowanie powielania kodu.
Mark Amery
4

Chciałem dodać moją krótką odpowiedź do tego już długiego wątku. Coś, co nie zostało wspomniane, to kolejność pierwszeństwa instrukcji catch, a dokładniej musisz być świadomy zakresu każdego rodzaju wyjątku, który próbujesz złapać.

Na przykład, jeśli użyjesz wyjątku „catch-all” jako wyjątku , będzie on poprzedzał wszystkie inne instrukcje catch i oczywiście wystąpią błędy kompilatora, jednak jeśli odwrócisz kolejność, możesz połączyć swoje instrukcje catch (trochę anty-wzorca) ) możesz umieścić typ wyjątku typu catch-all na dole, co spowoduje przechwycenie wyjątków, które nie spełniały wyższych wymagań w bloku try..catch:

            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);
            }

Bardzo polecam ludziom przejrzenie tego dokumentu MSDN:

Hierarchia wyjątków

Tahir Khalid
źródło
4

Może postaraj się uprościć kod, na przykład wstawiając wspólny kod do metody, tak jak w każdej innej części kodu, która nie znajduje się w klauzuli catch?

Na przykład:

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

// ...

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

Tak jak bym to zrobił, próba znalezienia prostoty jest pięknym wzorem

Żubrówka
źródło
3

Zauważ, że znalazłem jeden sposób, aby to zrobić, ale to bardziej przypomina materiał do The Daily WTF :

catch (Exception ex)
{
    switch (ex.GetType().Name)
    {
        case "System.FormatException":
        case "System.OverflowException":
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}
Michael Stum
źródło
9
-1 głos, +5 WTF :-) To nie powinno być oznaczone jako odpowiedź, ale jest obłędne.
Aaron,
1
Nie ma znaczenia, jak po prostu możemy to zrobić. Ale nie siedział bezczynnie i wpadł na pomysł, aby go rozwiązać. Bardzo doceniam.
Maxymus
2
Nie rób tego jednak, użyj Filtry wyjątków w C # 6 lub jakiejkolwiek innej odpowiedzi - umieszczam to tutaj jako „To jest jeden sposób, ale jest zły i chcę zrobić coś lepszego”.
Michael Stum
DLACZEGO to jest złe? Byłem zaskoczony, że nie można użyć wyjątku bezpośrednio w instrukcji switch.
MKesper
3
@MKesper Widzę kilka powodów, dla których jest źle. Wymaga napisania w pełni kwalifikowanych nazw klas jako literałów łańcuchowych, co jest podatne na literówki, przed którymi kompilator nie może cię uratować. (Jest to znaczące, ponieważ w wielu sklepach przypadki błędów są mniej dobrze przetestowane, a więc trywialne błędy w nich są bardziej prawdopodobne, że zostaną pominięte.) Nie uda się również dopasować wyjątku, który jest podklasą jednego z określonych przypadków. Ze względu na to, że są ciągami, przypadki zostaną pominięte przez narzędzia takie jak „Znajdź wszystkie referencje” VS - istotne, jeśli chcesz dodać krok czyszczenia wszędzie tam, gdzie wychwytuje się określony wyjątek.
Mark Amery
2

Warto tu wspomnieć. Możesz odpowiedzieć na wiele kombinacji (błąd wyjątku i komunikat wyjątku).

Natknąłem się na scenariusz przypadku użycia, gdy próbowałem rzutować obiekt kontrolny w siatce danych, z zawartością jako TextBox, TextBlock lub CheckBox. W tym przypadku zwrócony wyjątek był taki sam, ale komunikat był różny.

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
} 
Jerzy
źródło
0

Chcę zasugerować najkrótszą odpowiedź (jeszcze jeden funkcjonalny styl ):

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

W tym celu należy utworzyć kilka przeciążeń metody „Catch”, podobnych do 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));
    }

i tak wiele, ile chcesz. Ale musisz to zrobić raz i możesz użyć go we wszystkich swoich projektach (lub, jeśli stworzyłeś pakiet nuget, my też moglibyśmy go użyć).

I wdrożenie 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 Nie sprawdziłem null pod kątem prostoty kodu, rozważ dodanie walidacji parametrów.

ps2 Jeśli chcesz zwrócić wartość z catch, musisz wykonać te same metody Catch, ale z parametrami return i Func zamiast Action.

Eugene Gorbovoy
źródło
-15

Wystarczy zadzwonić do try i catch dwa razy.

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

To jest takie proste !!

uczony facet
źródło
3
um. jest to sprzeczne z celem pytania. Zadaje to pytanie, aby pozbyć się duplikatu kodu. ta odpowiedź dodaje więcej duplikatów kodu.
James Esh
-23

W wersji c # 6.0 Filtry wyjątków to ulepszenia w zakresie obsługi wyjątków

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
źródło
13
W tym przykładzie nie pokazano użycia filtrów wyjątków.
user247702
Jest to standardowy sposób filtrowania wyjątków w wersji c # 6.0
Kashif
5
Ponownie spójrz na to, czym dokładnie są filtry wyjątków. W swoim przykładzie nie używasz filtra wyjątków. Jest odpowiedni przykład w tej odpowiedzi opublikowanej rok wcześniej.
user247702
6
Przykładem filtrowania wyjątków może byćcatch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
saluce