Dlaczego spróbuj {…} ​​wreszcie {…} dobry; spróbować {…} złapać {} źle?

201

Widziałem, jak ludzie mówią, że złym sposobem jest użycie catch bez argumentów, szczególnie jeśli ten catch nic nie robi:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
catch   // No args, so it will catch any exception
{}
reader.Close();

Jest to jednak uważane za dobrą formę:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
finally   // Will execute despite any exception
{
  reader.Close();
}

O ile mogę stwierdzić, jedyną różnicą między umieszczeniem kodu czyszczącego w bloku na końcu a umieszczeniem kodu czyszczącego po blokach try..catch jest to, że w bloku try masz instrukcje return (w takim przypadku kod czyszczenia w końcu będzie uruchom, ale kod po try..catch nie będzie).

W przeciwnym razie, co jest w końcu takiego specjalnego?

mbeckish
źródło
7
Zanim spróbujesz złapać tygrysa, z którym nie możesz sobie poradzić, powinieneś udokumentować swoje życzenia w końcu.
Wyjątki temat w dokumentacji mogą dać dobry wgląd. Zobacz także przykład w końcu bloku .
Athafoud

Odpowiedzi:

357

Duża różnica polega na tym, try...catchże połknie wyjątek, ukrywając fakt, że wystąpił błąd. try..finallyuruchomi kod czyszczenia, a następnie wyjątek będzie kontynuowany, aby zająć się czymś, co wie, co z tym zrobić.

Khoth
źródło
11
Każdy kod napisany z myślą o enkapsulacji prawdopodobnie będzie w stanie obsłużyć wyjątek tylko w momencie jego wygenerowania. Po prostu przekazanie go z powrotem do stosu wywołań w desperackiej nadziei, że coś innego będzie w stanie poradzić sobie z jakimkolwiek wyjątkiem arbitrażowym, to przepis na katastrofę.
David Arno,
3
W większości przypadków bardziej oczywiste jest, dlaczego wystąpiłby określony wyjątek z poziomu aplikacji (np. Określone ustawienie konfiguracji) niż z poziomu biblioteki bibliotek klas.
Mark Cidade
88
David - wolałbym, aby program szybko się nie uruchamiał, aby uświadomić mi problem, który powoduje, że program działa w nieznanym stanie.
Erik Forbes,
6
Jeśli po wyjątku program jest w nieznanym stanie, kod jest nieprawidłowy.
Zan Lynx,
41
@DavidArno, każdy kod napisany z myślą o enkapsulacji powinien obsługiwać wyjątki tylko w swoim zakresie. Wszystko inne powinno być przekazane komuś innemu. Jeśli mam aplikację, która pobiera nazwę pliku od użytkowników, a następnie czyta plik, a mój czytnik plików otrzymuje wyjątek otwierający plik, powinien go przekazać (lub wykorzystać wyjątek i wyrzucić nowy), aby aplikacja mogła powiedzieć , hej - plik się nie otworzył, zapytaj użytkownika o inny. Czytnik plików nie powinien być w stanie monitować użytkowników ani podejmować innych działań w odpowiedzi. Jego jedynym celem jest odczyt plików.
iheanyi
62

„Wreszcie” to stwierdzenie „Coś, co zawsze musisz zrobić, aby upewnić się, że stan programu jest zdrowy”. W związku z tym zawsze dobrze jest go mieć, jeśli istnieje możliwość, że wyjątki mogą zrzucić stan programu. Kompilator dokłada również wszelkich starań, aby zapewnić uruchomienie kodu w końcu.

„Złap” to stwierdzenie „mogę odzyskać z tego wyjątku”. Powinieneś odzyskać tylko te wyjątki, które naprawdę możesz poprawić - łapanie bez argumentów mówi „Hej, mogę odzyskać wszystko!”, Co prawie zawsze jest nieprawdą.

Gdyby można było odzyskać po każdym wyjątku, to byłby to naprawdę spór semantyczny o tym, co deklarujesz. Jednak tak nie jest i prawie na pewno ramki powyżej twojej będą lepiej przygotowane do obsługi pewnych wyjątków. Jako taki, użyj w końcu, uruchom swój kod czyszczenia za darmo, ale nadal pozwól bardziej doświadczonym programom obsługi poradzić sobie z tym problemem.

Adam Wright
źródło
1
Twoje nastroje są powszechne, ale niestety ignoruje się inny ważny przypadek: wyraźne unieważnienie obiektu, którego niezmienniki mogą już nie mieć. Częstym wzorcem jest kod, aby uzyskać blokadę, wprowadzić pewne zmiany w obiekcie i zwolnić blokadę. Jeśli po wprowadzeniu niektórych, ale nie wszystkich zmian, wystąpi wyjątek, obiekt może pozostać w niepoprawnym stanie. Chociaż powinny istnieć lepsze alternatywy IMHO , nie znam lepszego podejścia niż wychwycenie wyjątku, który występuje, gdy stan obiektu może być nieprawidłowy, wyraźne unieważnienie stanu i ponowne wygenerowanie.
supercat
32

Ponieważ kiedy ta jedna linia generuje wyjątek, nie wiedziałbyś o tym.

W przypadku pierwszego bloku kodu wyjątek zostanie po prostu wchłonięty , program będzie kontynuował działanie, nawet jeśli stan programu może być nieprawidłowy.

Z drugiego bloku, wyjątek zostanie rzucony i pęcherzyki się jednakreader.Close() nadal jest gwarantowane do pracy.

Jeśli wyjątek nie jest oczekiwany, nie wstawiaj bloku try..catch, wtedy trudno będzie debugować później, gdy program przejdzie w zły stan i nie masz pojęcia, dlaczego.

czakryt
źródło
21

Wreszcie jest wykonywany bez względu na wszystko. Tak więc, jeśli twój blok try się powiódł, zostanie wykonany, jeśli blok try się nie powiedzie, wówczas wykona blok catch, a następnie blok w końcu.

Ponadto lepiej jest spróbować użyć następującej konstrukcji:

using (StreamReader reader=new  StreamReader("myfile.txt"))
{
}

Ponieważ instrukcja using jest automatycznie pakowana w try / wreszcie, a strumień zostanie automatycznie zamknięty. (Będziesz musiał umieścić try / catch wokół instrukcji using, jeśli chcesz złapać wyjątek).

Mark Ingram
źródło
5
To nie jest poprawne. Używanie nie zawinąć kod z try / catch, należy powiedzieć try / finally
pr0nin
8

Podczas gdy następujące 2 bloki kodu są równoważne, nie są równe.

try
{
  int i = 1/0; 
}
catch
{
  reader.Close();
  throw;
}

try
{
  int i = 1/0;
}
finally
{
  reader.Close();
}
  1. „w końcu” to kod ujawniający zamiary. Oświadczasz kompilatorowi i innym programistom, że ten kod musi zostać uruchomiony bez względu na wszystko.
  2. jeśli masz wiele bloków catch i masz kod czyszczenia, w końcu potrzebujesz. Bez tego powieliłbyś swój kod czyszczenia w każdym bloku catch. (Zasada OSUSZANIA)

wreszcie bloki są wyjątkowe. CLR rozpoznaje i traktuje kod za pomocą bloku ostatecznie niezależnie od bloków przechwytywania, a CLR dokłada wszelkich starań, aby zagwarantować, że blok ostatecznie będzie zawsze wykonywany. To nie tylko cukier syntaktyczny z kompilatora.

Robert Paulson
źródło
5

Zgadzam się z tym, co wydaje się tutaj konsensusem - pusty „haczyk” jest zły, ponieważ maskuje wszelkie wyjątki, które mogły wystąpić w bloku try.

Również z punktu widzenia czytelności, kiedy widzę blok „spróbuj”, zakładam, że będzie odpowiednia instrukcja „catch”. Jeśli używasz tylko „try”, aby upewnić się, że zasoby są rozdzielane w bloku „w końcu”, możesz zamiast tego rozważyć instrukcję „using” :

using (StreamReader reader = new StreamReader('myfile.txt'))
{
    // do stuff here
} // reader.dispose() is called automatically

Można użyć instrukcji „using” z dowolnym obiektem, który implementuje IDisposable. Metoda dispose () obiektu jest wywoływana automatycznie na końcu bloku.

Chris Lawlor
źródło
4

Posługiwać się Try..Catch..Finally , jeśli twoja metoda wie, jak obsługiwać wyjątek lokalnie. Wyjątek występuje w Try, Handled in Catch, a po zakończeniu czyszczenia w końcu.

W przypadku, gdy twoja metoda nie wie, jak obsłużyć wyjątek, ale wymaga czyszczenia po jego wystąpieniu, użyj Try..Finally

W ten sposób wyjątek jest propagowany do metod wywołujących i obsługiwany, jeśli w metodach wywołujących znajdują się odpowiednie instrukcje Catch. Jeśli w bieżącej metodzie lub żadnej z metod wywołujących nie ma żadnych procedur obsługi wyjątków, aplikacja ulega awarii.

Dzięki Try..Finallytemu zapewnione jest lokalne czyszczenie przed propagowaniem wyjątku do metod wywołujących.

manjuv
źródło
1
Jakkolwiek podstawowa jest ta odpowiedź, jest ona absolutnie najlepsza. Dobrze jest po prostu próbować / złapać / wreszcie, nawet jeśli jedno z dwóch ostatnich pozostanie puste. Istnieją BARDZO RZADKIE sytuacje, w których blok catch może istnieć i być pusty, ale przynajmniej jeśli zawsze piszesz try / catch / wreszcie, zobaczysz pusty blok podczas przeglądania kodu. Posiadanie pustego bloku jest pomocne w ten sam sposób. Jeśli potrzebujesz późniejszego czyszczenia lub chcesz zdebugować stan w czasie wyjątku, jest to niezwykle pomocne.
Jesse Williams
3

Blok try..finally będzie nadal zgłaszał wszelkie zgłoszone wyjątki. Wszystko finallyto zapewnia uruchomienie kodu czyszczenia przed zgłoszeniem wyjątku.

Try..catch z pustym haczykiem całkowicie wykorzysta każdy wyjątek i ukryje fakt, że tak się stało. Czytnik zostanie zamknięty, ale nie wiadomo, czy coś się stało. Co jeśli Twoim zamiarem było zapisanie „ i” do pliku? W takim przypadku nie dojdziesz do tej części kodu, a plik myfile.txt będzie pusty. Czy wszystkie pozostałe metody radzą sobie z tym poprawnie? Kiedy zobaczysz pusty plik, czy będziesz w stanie poprawnie zgadnąć, że jest pusty, ponieważ został zgłoszony wyjątek? Lepiej rzucić wyjątek i dać znać, że robisz coś złego.

Innym powodem jest try..catch zrobiony w ten sposób jest całkowicie niepoprawny. W ten sposób mówisz: „Nie ważne, co się stanie, dam sobie radę”. A co StackOverflowExceptionpotem możesz posprzątać? Co OutOfMemoryException? Zasadniczo powinieneś obsługiwać tylko te wyjątki, których się spodziewasz i wiesz, jak sobie z tym poradzić.

OwenP
źródło
2

Jeśli nie wiesz, jaki typ wyjątku chcesz złapać lub co z nim zrobić, nie ma sensu mieć instrukcji catch. Powinieneś zostawić to wyższemu rozmówcy, który może mieć więcej informacji o sytuacji, aby wiedzieć, co robić.

Wciąż powinieneś mieć w końcu instrukcję na wypadek, gdyby istniał wyjątek, abyś mógł wyczyścić zasoby, zanim ten wyjątek zostanie zgłoszony dzwoniącemu.

Mark Cidade
źródło
2

Z punktu widzenia czytelności bardziej wyraźnie mówi przyszłym czytnikom kodu: „te rzeczy tutaj są ważne, należy to robić bez względu na to, co się stanie”. To jest dobre.

Ponadto puste deklaracje połowowe mają zwykle pewien „zapach”. Mogą być znakiem, że programiści nie zastanawiają się nad różnymi wyjątkami, które mogą wystąpić i jak sobie z nimi poradzić.

Ryan
źródło
2

Wreszcie jest opcjonalny - nie ma powodu, aby mieć blok „Wreszcie”, jeśli nie ma zasobów do wyczyszczenia.

Guy Starbuck
źródło
2

Zaczerpnięte z: tutaj

Zgłaszanie i wychwytywanie wyjątków nie powinno odbywać się rutynowo w ramach pomyślnego wykonania metody. Podczas opracowywania bibliotek klas kod klienta musi mieć możliwość przetestowania pod kątem błędu przed podjęciem operacji, która może spowodować zgłoszenie wyjątku. Na przykład System.IO.FileStream zapewnia właściwość CanRead, którą można sprawdzić przed wywołaniem metody Read, zapobiegając potencjalnemu wyjątkowi, jak pokazano w poniższym fragmencie kodu:

Dim str As Stream = GetStream () If (str.CanRead) Następnie kod do odczytu strumienia End If

Decyzja o sprawdzeniu stanu obiektu przed wywołaniem określonej metody, która może zgłosić wyjątek, zależy od oczekiwanego stanu obiektu. Jeśli obiekt FileStream zostanie utworzony przy użyciu ścieżki pliku, która powinna istnieć, i konstruktora, który powinien zwrócić plik w trybie odczytu, sprawdzenie właściwości CanRead nie jest konieczne; niemożność odczytania FileStream stanowiłaby naruszenie oczekiwanego zachowania wywoływanych metod i należy zgłosić wyjątek. Natomiast jeśli metoda jest udokumentowana jako zwracająca odwołanie FileStream, które może, ale nie musi, być czytelne, wskazane jest sprawdzenie właściwości CanRead przed próbą odczytania danych.

Aby zilustrować wpływ na wydajność, jaki może powodować technika kodowania „uruchom aż do wyjątku”, wydajność rzutowania, który zgłasza wyjątek InvalidCastException w przypadku niepowodzenia rzutowania, jest porównywana z operatorem C # jako operatorem, który zwraca wartości zerowe w przypadku niepowodzenia rzutowania. Wydajność tych dwóch technik jest identyczna w przypadku, gdy rzutowanie jest ważne (patrz Test 8.05), ale w przypadku, gdy rzutowanie jest nieprawidłowe, a użycie rzutowania powoduje wyjątek, użycie rzutowania jest 600 razy wolniejsze niż użycie jako operator (patrz Test 8.06). Wysokowydajny wpływ techniki zgłaszania wyjątków obejmuje koszt alokacji, zgłaszania i przechwytywania wyjątku oraz koszt późniejszego wyrzucania elementów bezużytecznych, co oznacza, że ​​natychmiastowy wpływ zgłoszenia wyjątku nie jest tak wysoki. Gdy pojawi się więcej wyjątków,

SpoiledTechie.com
źródło
2
Scott - jeśli cytowany powyżej tekst znajduje się za zaporą witrynysexsex.com, prawdopodobnie nie powinieneś tego tutaj zamieszczać. Mogę się mylić, ale założę się, że to nie jest dobry pomysł.
Onorio Catenacci
2

Złą praktyką jest dodawanie klauzuli catch tylko w celu ponownego zwrócenia wyjątku.

Bastien Vandamme
źródło
2

Jeśli przeczytasz C # dla programistów , zrozumiesz, że w końcu blok został zaprojektowany w celu optymalizacji aplikacji i zapobiegania wyciekom pamięci.

CLR nie eliminuje całkowicie wycieków ... wycieki pamięci mogą wystąpić, jeśli program niechcący zachowa odniesienia do niepożądanych obiektów

Na przykład po otwarciu połączenia z plikiem lub bazą danych urządzenie przydzieli pamięć, aby obsłużyć tę transakcję, i pamięć ta nie zostanie zachowana, dopóki nie zostanie wykonane polecenie usuwania lub zamknięcia. ale jeśli podczas transakcji wystąpił błąd, polecenie kontynuowania zostanie zakończone nie, chyba że będzie w try.. finally..bloku.

catchróżni się od finallytego, że catch został zaprojektowany tak, aby dać ci możliwość obsługi / zarządzania lub interpretacji samego błędu. Pomyśl o tym jako o osobie, która mówi: „hej, złapałem złych facetów, co chcesz, żebym im zrobił?” podczasfinally został zaprojektowany, aby upewnić się, że Twoje zasoby zostały poprawnie umieszczone. Pomyśl o kimś, kto bez względu na to, czy jest jakiś zły człowiek, upewni się, że twoja nieruchomość jest nadal bezpieczna.

I powinieneś pozwolić tym dwóm na dobre pracować razem.

na przykład:

try
{
  StreamReader reader=new  StreamReader("myfile.txt");
  //do other stuff
}
catch(Exception ex){
 // Create log, or show notification
 generic.Createlog("Error", ex.message);
}
finally   // Will execute despite any exception
{
  reader.Close();
}
dr.Crow
źródło
1

Wreszcie możesz wyczyścić zasoby, nawet jeśli instrukcja catch zgłasza wyjątek do programu wywołującego. W twoim przykładzie zawierającym pustą instrukcję catch różnica jest niewielka. Jednak jeśli złapiesz, wykonasz pewne przetwarzanie i zgłaszasz błąd, a nawet po prostu w ogóle go nie złapiesz, w końcu nadal będzie działać.

Kibee
źródło
1

Cóż, po pierwsze, złym zwyczajem jest wychwytywanie wyjątków, z którymi sobie nie radzisz. Zapoznaj się z rozdziałem 5 na temat wydajności .NET dzięki poprawie wydajności i skalowalności aplikacji .NET . Na marginesie, prawdopodobnie powinieneś ładować strumień wewnątrz bloku try, w ten sposób możesz złapać odpowiedni wyjątek, jeśli się nie powiedzie. Utworzenie strumienia poza blokiem try nie wystarcza.

Factor Mystic
źródło
0

Prawdopodobnie z wielu powodów wyjątki są wykonywane bardzo wolno. Jeśli tak się często zdarza, możesz łatwo skrócić czas wykonywania.

dużo czasu wolnego
źródło
0

Problem z blokami try / catch, które wychwytują wszystkie wyjątki, polega na tym, że program jest teraz w nieokreślonym stanie, jeśli wystąpi nieznany wyjątek. Jest to całkowicie sprzeczne z regułą szybkiego niepowodzenia - nie chcesz, aby Twój program kontynuował działanie, jeśli wystąpi wyjątek. Powyższe try / catch może nawet wychwycić wyjątki OutOfMemoryException, ale jest to z pewnością stan, w którym Twój program nie uruchomi się.

Bloki Try / wreszcie pozwalają na wykonanie czyszczenia kodu, ale nadal nie działają szybko. W większości przypadków chcesz wychwycić wszystkie wyjątki na poziomie globalnym, aby móc je zarejestrować, a następnie wyjść.

David Mohundro
źródło
0

Efektywna różnica między twoimi przykładami jest znikoma, dopóki nie zostaną zgłoszone wyjątki.

Jeśli jednak zostanie zgłoszony wyjątek w klauzuli „try”, pierwszy przykład całkowicie go pochłonie. Drugi przykład podniesie wyjątek do następnego kroku w górę stosu wywołań, więc różnica w podanych przykładach polega na tym, że jeden całkowicie ukrywa wszelkie wyjątki (pierwszy przykład), a drugi (drugi przykład) zachowuje informacje o wyjątkach do potencjalnej późniejszej obsługi, podczas gdy nadal wykonuję treść w klauzuli „w końcu”.

Jeśli na przykład umieścisz kod w klauzuli „catch” z pierwszego przykładu, który zgłosił wyjątek (albo ten, który został początkowo zgłoszony, albo nowy), kod czyszczenia czytnika nigdy nie zostanie wykonany. Wreszcie wykonuje się niezależnie od tego, co dzieje się w klauzuli „catch”.

Główną różnicą między „catch” i „wreszcie” jest to, że zawartość bloku „w końcu” (z kilkoma rzadkimi wyjątkami) można uznać za gwarantowaną do wykonania, nawet w obliczu nieoczekiwanego wyjątku, podczas gdy dowolny kod następuje klauzula „catch” (ale poza klauzulą ​​„w końcu”) nie zapewniłaby takiej gwarancji.

Nawiasem mówiąc, Stream i StreamReader zarówno implementują IDisposable, jak i mogą być zawinięte w blok „za pomocą”. Bloki „using” są semantycznym odpowiednikiem try / wreszcie (brak „catch”), więc twój przykład może być bardziej zwięźle wyrażony jako:

using (StreamReader reader = new  StreamReader("myfile.txt"))
{
  int i = 5 / 0;
}

... która zamknie i usunie instancję StreamReader, gdy wyjdzie poza zakres. Mam nadzieję że to pomoże.

Jared
źródło
0

spróbuj {…} ​​catch {} nie zawsze jest zły. To nie jest powszechny wzorzec, ale zwykle używam go, gdy muszę zamknąć zasoby bez względu na wszystko, na przykład zamknięcie (ewentualnie) otwartych gniazd na końcu wątku.

Martin Liesén
źródło