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.Empty
raz?
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ść.
c#
.net
exception
exception-handling
Michael Stum
źródło
źródło
AggregateException
:Odpowiedzi:
Złap
System.Exception
i włącz typyźródło
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ć:
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?
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:
... 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.
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ą
ErrorHandler
funkcję i wywołaj ją z każdego bloku catch.Jeśli zapytasz mnie, drugi przykład (ze słowami kluczowymi
if
iis
) 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
if
testu 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ę...
źródło
Exception
i 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.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ć).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.Func<Exception, MyEnumType>
zamiastAction<Exception>
. To znaczyFunc<T, Result>
,Result
że jest to typ zwrotu.Jak zauważyli inni,
if
w 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ć:MyFilter
Metoda może wtedy wyglądać następująco:Alternatywnie można to wszystko zrobić w wierszu (prawa strona instrukcji when musi być tylko wyrażeniem logicznym).
Różni się to od używania
if
instrukcji zcatch
bloku, 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:
W momencie pisania tego tekstu będzie to obejmować obsługę C # 6.
źródło
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
Możesz użyć anonimowej funkcji do enkapsulacji kodu błędu, a następnie wywołać go w tych konkretnych blokach catch:
źródło
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ąć!Ze względu na kompletność, od .NET 4.0 kod można przepisać jako:
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:
Możesz także utworzyć metody analizowania zwracanych krotek, które nie są jeszcze dostępne w .NET Framework od wersji 4.6:
I użyj ich w ten sposób:
Następna bezużyteczna aktualizacja tej bezużytecznej odpowiedzi pojawia się, gdy dekonstrukcja parametrów wyjściowych jest zaimplementowana w C # 12. :)
źródło
Guid.TryParse
nigdy nie wracaGuid.Empty
. Jeśli łańcuch ma niepoprawny format, ustawiaresult
parametr wyjściowy naGuid.Empty
, ale zwracafalse
. Wspominam o tym, ponieważ widziałem kod, który robi rzeczy w styluGuid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }
, co zwykle jest złe, jeślis
mogłoby to być ciąg znakówGuid.Empty
.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.Filtry wyjątków są teraz dostępne w wersji c # 6+. Możesz to zrobić
W wersji C # 7.0+ można to również połączyć z dopasowaniem wzorca
źródło
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:
Niektórzy uważają, że ten kod jest taki sam jak
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ę .
źródło
Jeśli nie chcesz używać
if
oświadczenie obrębiecatch
zakresów, wC# 6.0
można użyćException Filters
składni , która została już obsługiwany przez CLR w wersji Zapowiedzi ale istniała tylko wVB.NET
/MSIL
:Ten kod złapie
Exception
tylko wtedy, gdy jest toInvalidDataException
lubArgumentNullException
.Właściwie można umieścić w zasadzie dowolny warunek w tej
when
klauzuli:Zauważ, że w przeciwieństwie do
if
instrukcji wewnątrzcatch
zakresu,Exception Filters
nie można wyrzucićExceptions
, a kiedy to zrobią lub gdy warunek nie zostanie spełnionytrue
, następnycatch
warunek zostanie oceniony:Jeśli jest więcej niż jeden
true
Exception Filter
- pierwszy zostanie zaakceptowany:I jak widać w
MSIL
kodzie, kod nie jest tłumaczony naif
instrukcje, ale naFilters
iExceptions
nie można go wyrzucić z obszarów oznaczonychFilter 1
i,Filter 2
ale filtr rzucającyException
nie powiedzie się, również ostatnia wartość porównania wypchnięta na stos przedendfilter
poleceniem określi sukces / awarię filtra (Catch 1
XORCatch 2
wykona się odpowiednio):Również konkretnie
Guid
ma tęGuid.TryParse
metodę.źródło
Dzięki C # 7 odpowiedź od Michaela Stuma może zostać ulepszona przy zachowaniu czytelności instrukcji switch:
I z C # 8 jako wyrażeniem przełącznika:
źródło
when
jest o wiele bardziej eleganckie / odpowiednie niż przełącznik.catch
blok, lub zresztą musisz go rzucić .. Z mojego doświadczenia wynika , żethrow;
w twoimcatch
bloku jest prawdopodobnie zapach kodu.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ł:
źródło
is
operatora 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.is
operator obniża wydajność, jest późniejsze wykonanieas
operacji (stąd niepotrzebnie kwalifikują regułę ). Jeśli wszystko, co robisz, to testowanie obsady bez potrzeby jej wykonywania, tois
operator jest dokładnie tym, czego chcesz użyć.w C # 6 zalecanym podejściem jest użycie filtrów wyjątków, oto przykład:
źródło
To jest wariant odpowiedzi Matta (wydaje mi się, że jest to trochę czystsze) ... użyj metody:
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:źródło
WebId = Guid.Emtpy
w przypadku, gdy nie zgłoszono wyjątku.return
do mojej odpowiedzi. Dzięki za wkład.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.
Istnieje kilka zalet odwracania wyrażenia:
Można go nawet spakować do pojedynczej linii (choć niezbyt ładnej)
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:
źródło
return
, chociaż odwrócenie warunku jest również nieco lepsze.@Micheal
Nieznacznie zmieniona wersja twojego kodu:
Porównywanie ciągów znaków jest brzydkie i wolne.
źródło
throw new FormatException();
gothrow 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ł==
zamiastis
(lub po prostucatch (FormatException)
).Co powiesz na
źródło
źródło
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:
(Zasadniczo zapewnia kolejne puste
Catch
przeciążenie, które zwraca się)Ważniejsze pytanie brzmi: dlaczego . Nie sądzę, że koszt przeważa tutaj zysk :)
źródło
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
type
sprawdzania 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ęcex is ...
zachowywało się niepożądane zachowanie, ponieważ klasy pozbawione są przypisywane do typów nadrzędnych).Stosowanie
Rozszerzenie IsAnyOf.cs (Zobacz przykład pełnej obsługi błędów dla zależności)
Przykład pełnej obsługi błędów (kopiuj-wklej do nowej aplikacji konsoli)
Dwa przykładowe testy jednostek NUnit
Zachowanie dopasowania dla
Exception
typów jest dokładne (tzn. Dziecko NIE JEST dopasowaniem dla żadnego z jego typów rodzicielskich).źródło
when
, tak jak każda wersjacatch (Exception ex) {if (...) {/*handle*/} throw;}
. Rzeczywistą wartościąwhen
jest 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.IsAnyOf
metoda może być przepisana w prosty sposóbp_comparisons.Contains(p_parameter)
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:
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):
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:
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:
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:
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:
Podsumowując:
źródło
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:
Kod wygląda więc tak:
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ę:
źródło
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:
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:
źródło
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:
Bardzo polecam ludziom przejrzenie tego dokumentu MSDN:
Hierarchia wyjątków
źródło
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:
Tak jak bym to zrobił, próba znalezienia prostoty jest pięknym wzorem
źródło
Zauważ, że znalazłem jeden sposób, aby to zrobić, ale to bardziej przypomina materiał do The Daily WTF :
źródło
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.
źródło
Chcę zasugerować najkrótszą odpowiedź (jeszcze jeden funkcjonalny styl ):
W tym celu należy utworzyć kilka przeciążeń metody „Catch”, podobnych do System.Action:
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:
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.
źródło
Wystarczy zadzwonić do try i catch dwa razy.
To jest takie proste !!
źródło
W wersji c # 6.0 Filtry wyjątków to ulepszenia w zakresie obsługi wyjątków
źródło
catch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }