Warum ist versuchen {…} endlich {…} gut; versuchen {{} fangen {} schlecht?

201

Ich habe Leute sagen sehen, dass es eine schlechte Form ist, catch ohne Argumente zu verwenden, besonders wenn dieser catch nichts bewirkt:

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

Dies wird jedoch als gute Form angesehen:

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

Soweit ich das beurteilen kann, besteht der einzige Unterschied zwischen dem Einfügen von Bereinigungscode in einen finally-Block und dem Einfügen von Bereinigungscode nach den try..catch-Blöcken darin, dass Sie return-Anweisungen in Ihrem try-Block haben (in diesem Fall wird der Bereinigungscode in finally-Blöcken verwendet ausführen, aber Code nach dem try..catch wird nicht).

Ansonsten, was ist das Besondere an Endlich?

mbeckisch
quelle
7
Bevor Sie versuchen, einen Tiger zu fangen, mit dem Sie nicht umgehen können, sollten Sie Ihre letzten Wünsche dokumentieren.
Das Ausnahmethema in der Dokumentation kann einige gute Einblicke geben. Schauen Sie sich auch das Beispiel " Endlich blockieren" an .
Athafoud

Antworten:

357

Der große Unterschied besteht darin, dass try...catchdie Ausnahme verschluckt wird und die Tatsache verborgen bleibt, dass ein Fehler aufgetreten ist. try..finallyführt Ihren Bereinigungscode aus und dann wird die Ausnahme fortgesetzt, um von etwas behandelt zu werden, das weiß, was damit zu tun ist.

Khoth
quelle
11
Jeder Code, der unter Berücksichtigung der Kapselung geschrieben wurde, kann die Ausnahme wahrscheinlich nur an dem Punkt behandeln, an dem sie ausgelöst wird. Es ist ein Rezept für eine Katastrophe, es einfach wieder in den Call-Stack zu geben, in der verzweifelten Hoffnung, dass etwas anderes eine willkürliche Ausnahme bewältigen kann.
David Arno
3
In den meisten Fällen ist es offensichtlicher, warum eine bestimmte Ausnahme auf Anwendungsebene (z. B. eine bestimmte Konfigurationseinstellung) auftritt als auf Klassenbibliotheksebene.
Mark Cidade
88
David - Ich würde es vorziehen, wenn das Programm schnell fehlschlägt, damit ich auf das Problem aufmerksam gemacht werden kann, anstatt das Programm in einem unbekannten Zustand laufen zu lassen.
Erik Forbes
6
Wenn sich Ihr Programm nach einer Ausnahme in einem unbekannten Zustand befindet, machen Sie den Code falsch.
Zan Lynx
41
@DavidArno, jeder Code, der unter Berücksichtigung der Kapselung geschrieben wurde, sollte nur Ausnahmen in ihrem Bereich behandeln. Alles andere sollte weitergegeben werden, damit jemand anderes damit umgehen kann. Wenn ich eine Anwendung habe, die einen Dateinamen von Benutzern erhält und dann die Datei liest und mein Dateireader eine Ausnahme beim Öffnen der Datei erhält, sollte er sie weitergeben (oder die Ausnahme verbrauchen und eine neue auslösen), damit die Anwendung sagen kann , hey - Datei wurde nicht geöffnet, lassen Sie uns den Benutzer zur Eingabe einer anderen auffordern. Der Dateireader sollte nicht in der Lage sein, Benutzer aufzufordern oder andere Maßnahmen zu ergreifen. Der einzige Zweck ist das Lesen von Dateien.
Iheanyi
62

"Schließlich" ist eine Aussage von "Etwas, das Sie immer tun müssen, um sicherzustellen, dass der Programmstatus gesund ist". Daher ist es immer eine gute Form, eine zu haben, wenn die Möglichkeit besteht, dass Ausnahmen den Programmstatus beeinträchtigen. Der Compiler unternimmt auch große Anstrengungen, um sicherzustellen, dass Ihr Endlich-Code ausgeführt wird.

"Catch" ist eine Aussage von "Ich kann mich von dieser Ausnahme erholen". Sie sollten sich nur von Ausnahmen erholen, die Sie wirklich korrigieren können - catch ohne Argumente sagt "Hey, ich kann mich von allem erholen!", Was fast immer falsch ist.

Wenn es möglich wäre, sich von jeder Ausnahme zu erholen, wäre es wirklich ein semantischer Streit darüber, was Sie als Ihre Absicht deklarieren. Dies ist jedoch nicht der Fall, und mit ziemlicher Sicherheit sind Frames über Ihren für bestimmte Ausnahmen besser gerüstet. Verwenden Sie daher endlich, lassen Sie Ihren Bereinigungscode kostenlos ausführen, und lassen Sie dennoch sachkundigere Handler das Problem lösen.

Adam Wright
quelle
1
Ihr Gefühl ist weit verbreitet, ignoriert aber leider einen anderen wichtigen Fall: die ausdrückliche Ungültigmachung eines Objekts, dessen Invarianten möglicherweise nicht mehr gelten. Ein gängiges Muster besteht darin, dass Code eine Sperre erhält, einige Änderungen an einem Objekt vornimmt und die Sperre aufhebt. Wenn nach einigen, aber nicht allen Änderungen eine Ausnahme auftritt, bleibt das Objekt möglicherweise in einem ungültigen Zustand. Obwohl meiner Meinung nach bessere Alternativen existieren sollten , kenne ich keinen besseren Ansatz, als eine Ausnahme abzufangen, die auftritt, während der Objektstatus möglicherweise ungültig ist, den Status ausdrücklich ungültig macht und erneut wirft.
Supercat
32

Denn wenn diese eine einzelne Zeile eine Ausnahme auslöst, würden Sie es nicht wissen.

Mit dem ersten Codeblock wird die Ausnahme einfach absorbiert und das Programm wird auch dann weiter ausgeführt, wenn der Status des Programms möglicherweise falsch ist.

Mit dem zweiten Block wird die Ausnahme ausgelöst und sprudelt, aber die reader.Close()Ausführung wird garantiert.

Wenn eine Ausnahme nicht erwartet wird, setzen Sie keinen try..catch-Block, es ist später schwierig zu debuggen, wenn das Programm in einen schlechten Zustand versetzt wurde und Sie keine Ahnung haben, warum.

Chakrit
quelle
21

Schließlich wird egal was ausgeführt. Wenn Ihr try-Block erfolgreich war, wird er ausgeführt. Wenn Ihr try-Block fehlschlägt, wird der catch-Block und dann der finally-Block ausgeführt.

Versuchen Sie auch, das folgende Konstrukt zu verwenden:

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

Da die using-Anweisung automatisch in einen try / finally-Code eingeschlossen wird, wird der Stream automatisch geschlossen. (Sie müssen die using-Anweisung mit try / catch versehen, wenn Sie die Ausnahme tatsächlich abfangen möchten.)

Mark Ingram
quelle
5
Das ist nicht richtig. Mit wird der Code nicht mit try / catch umbrochen, sondern sollte try / finally
pr0nin
8

Die folgenden 2 Codeblöcke sind zwar äquivalent, aber nicht gleich.

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

try
{
  int i = 1/0;
}
finally
{
  reader.Close();
}
  1. 'endlich' ist absichtsbewusster Code. Sie erklären dem Compiler und anderen Programmierern, dass dieser Code ausgeführt werden muss, egal was passiert.
  2. Wenn Sie mehrere Catch-Blöcke haben und Bereinigungscode haben, müssen Sie endlich. Ohne endgültig würden Sie Ihren Bereinigungscode in jedem catch-Block duplizieren. (TROCKENES Prinzip)

Schließlich sind Blöcke etwas Besonderes. Die CLR erkennt und behandelt Code mit einem finally-Block getrennt von catch-Blöcken, und die CLR unternimmt große Anstrengungen, um sicherzustellen, dass ein finally-Block immer ausgeführt wird. Es ist nicht nur syntaktischer Zucker vom Compiler.

Robert Paulson
quelle
5

Ich stimme dem zu, was hier der Konsens zu sein scheint - ein leerer "Fang" ist schlecht, weil er jede Ausnahme maskiert, die im try-Block aufgetreten sein könnte.

Unter dem Gesichtspunkt der Lesbarkeit gehe ich auch davon aus, dass es eine entsprechende 'catch'-Anweisung gibt, wenn ich einen' try'-Block sehe. Wenn Sie nur einen 'try' verwenden, um sicherzustellen, dass die Ressourcen im 'finally'-Block freigegeben werden, können Sie stattdessen die ' using'-Anweisung in Betracht ziehen :

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

Sie können die Anweisung 'using' für jedes Objekt verwenden, das IDisposable implementiert. Die dispose () -Methode des Objekts wird am Ende des Blocks automatisch aufgerufen.

Chris Lawlor
quelle
4

Verwenden Try..Catch..FinallySie diese Option , wenn Ihre Methode weiß, wie die Ausnahme lokal behandelt wird. Die Ausnahme tritt in Try, Handled in Catch und danach in Endlich auf.

Falls Ihre Methode nicht weiß, wie die Ausnahme zu behandeln ist, aber eine Bereinigung benötigt, sobald sie aufgetreten ist, verwenden Sie sie Try..Finally

Auf diese Weise wird die Ausnahme an die aufrufenden Methoden weitergegeben und behandelt, wenn die aufrufenden Methoden geeignete Catch-Anweisungen enthalten. Wenn die aktuelle Methode oder eine der aufrufenden Methoden keine Ausnahmebehandlungsroutinen enthält, stürzt die Anwendung ab.

Dadurch Try..Finallywird sichergestellt, dass die lokale Bereinigung durchgeführt wird, bevor die Ausnahme an die aufrufenden Methoden weitergegeben wird.

Manjuv
quelle
1
So einfach diese Antwort auch ist, sie ist absolut die beste. Es ist gut, einfach die Angewohnheit zu haben, es zu versuchen / zu fangen / endlich, auch wenn einer der beiden letzteren leer bleibt. Es gibt SEHR SELTENE Umstände, in denen ein catch-Block vorhanden und leer sein kann. Wenn Sie jedoch immer try / catch / finally schreiben, wird der leere Block angezeigt, wenn Sie den Code lesen. Ein leerer finally-Block ist auf die gleiche Weise hilfreich. Wenn Sie später bereinigen müssen oder zum Zeitpunkt der Ausnahme einen Status debuggen müssen, ist dies unglaublich hilfreich.
Jesse Williams
3

Der try..finally-Block löst weiterhin alle Ausnahmen aus, die ausgelöst werden. Sie finallymüssen lediglich sicherstellen, dass der Bereinigungscode ausgeführt wird, bevor die Ausnahme ausgelöst wird.

Der try..catch mit einem leeren catch verbraucht jede Ausnahme vollständig und verbirgt die Tatsache, dass es passiert ist. Der Leser wird geschlossen, aber es ist nicht abzusehen, ob das Richtige passiert ist. Was wäre, wenn Sie beabsichtigen würden, i in die Datei zu schreiben ? In diesem Fall schaffen Sie es nicht zu diesem Teil des Codes und myfile.txt ist leer. Behandeln alle nachgeschalteten Methoden dies ordnungsgemäß? Wenn Sie die leere Datei sehen, können Sie dann richtig erraten, dass sie leer ist, weil eine Ausnahme ausgelöst wurde? Es ist besser, die Ausnahme auszulösen und bekannt zu geben, dass Sie etwas falsch machen.

Ein weiterer Grund ist der Versuch ... ein solcher Fang ist völlig falsch. Was Sie damit sagen, ist: "Egal was passiert, ich kann damit umgehen." Was ist mit StackOverflowException, kannst du danach aufräumen? Was ist mit OutOfMemoryException? Im Allgemeinen sollten Sie nur Ausnahmen behandeln, die Sie erwarten und die Sie behandeln können.

OwenP
quelle
2

Wenn Sie nicht wissen, welchen Ausnahmetyp Sie abfangen sollen oder was Sie damit tun sollen, macht es keinen Sinn, eine catch-Anweisung zu haben. Sie sollten es nur einem höheren Anrufer überlassen, der möglicherweise mehr Informationen über die Situation hat, um zu wissen, was zu tun ist.

Sie sollten dort immer noch eine finally-Anweisung haben, falls es eine Ausnahme gibt, damit Sie Ressourcen bereinigen können, bevor diese Ausnahme an den Aufrufer gesendet wird.

Mark Cidade
quelle
2

Aus Sicht der Lesbarkeit wird zukünftigen Code-Lesern expliziter gesagt: "Dieses Zeug hier ist wichtig, es muss getan werden, egal was passiert." Das ist gut.

Außerdem neigen leere Fanganweisungen dazu, einen bestimmten "Geruch" zu haben. Sie könnten ein Zeichen dafür sein, dass Entwickler nicht über die verschiedenen Ausnahmen nachdenken, die auftreten können, und wie sie damit umgehen sollen.

Ryan
quelle
2

Schließlich ist optional - es gibt keinen Grund, einen "Endlich" -Block zu haben, wenn keine Ressourcen zum Bereinigen vorhanden sind.

Guy Starbuck
quelle
2

Entnommen aus: hier

Das Auslösen und Abfangen von Ausnahmen sollte im Rahmen der erfolgreichen Ausführung einer Methode nicht routinemäßig erfolgen. Bei der Entwicklung von Klassenbibliotheken muss dem Clientcode die Möglichkeit gegeben werden, auf eine Fehlerbedingung zu testen, bevor eine Operation ausgeführt wird, die dazu führen kann, dass eine Ausnahme ausgelöst wird. Beispielsweise bietet System.IO.FileStream eine CanRead-Eigenschaft, die vor dem Aufrufen der Read-Methode überprüft werden kann, um zu verhindern, dass eine potenzielle Ausnahme ausgelöst wird, wie im folgenden Codeausschnitt dargestellt:

Dim str As Stream = GetStream () If (str.CanRead) Dann 'Code zum Lesen des Streams End If

Die Entscheidung, ob der Status eines Objekts vor dem Aufrufen einer bestimmten Methode überprüft werden soll, die eine Ausnahme auslösen kann, hängt vom erwarteten Status des Objekts ab. Wenn ein FileStream-Objekt mit einem Dateipfad erstellt wird, der vorhanden sein sollte, und einem Konstruktor, der eine Datei im Lesemodus zurückgeben soll, ist das Überprüfen der CanRead-Eigenschaft nicht erforderlich. Die Unfähigkeit, den FileStream zu lesen, würde das erwartete Verhalten der durchgeführten Methodenaufrufe verletzen, und es sollte eine Ausnahme ausgelöst werden. Wenn im Gegensatz dazu dokumentiert ist, dass eine Methode eine FileStream-Referenz zurückgibt, die möglicherweise lesbar ist oder nicht, empfiehlt es sich, die CanRead-Eigenschaft zu überprüfen, bevor Sie versuchen, Daten zu lesen.

Um die Auswirkungen auf die Leistung zu veranschaulichen, die die Verwendung einer Codierungstechnik "Bis Ausnahme ausführen" verursachen kann, wird die Leistung einer Umwandlung, die eine InvalidCastException auslöst, wenn die Umwandlung fehlschlägt, mit dem Operator C # as verglichen, der Nullen zurückgibt, wenn eine Umwandlung fehlschlägt. Die Leistung der beiden Techniken ist für den Fall identisch, in dem die Besetzung gültig ist (siehe Test 8.05), aber für den Fall, dass die Besetzung ungültig ist und die Verwendung einer Besetzung eine Ausnahme verursacht, ist die Verwendung einer Besetzung 600-mal langsamer als die Verwendung von als Bediener (siehe Test 8.06). Die Hochleistungseffekte der Ausnahmeauslösungstechnik umfassen die Kosten für das Zuweisen, Auslösen und Abfangen der Ausnahme sowie die Kosten für die nachfolgende Speicherbereinigung des Ausnahmeobjekts, was bedeutet, dass die unmittelbare Auswirkung des Auslösens einer Ausnahme nicht so hoch ist. Wenn mehr Ausnahmen geworfen werden,

SpoiledTechie.com
quelle
2
Scott - Wenn der oben zitierte Text hinter der Paywall von Expertsexchange.com steht, sollten Sie ihn wahrscheinlich nicht hier posten. Ich kann mich irren, aber ich würde wetten, dass das keine gute Idee ist.
Onorio Catenacci
2

Es ist eine schlechte Praxis, eine catch-Klausel hinzuzufügen, um die Ausnahme erneut auszulösen.

Bastien Vandamme
quelle
2

Wenn Sie C # für Programmierer lesen, werden Sie verstehen, dass der finally-Block dazu gedacht war, eine Anwendung zu optimieren und Speicherverluste zu verhindern.

Die CLR beseitigt Lecks nicht vollständig ... Speicherlecks können auftreten, wenn das Programm versehentlich Verweise auf unerwünschte Objekte behält

Wenn Sie beispielsweise eine Datei- oder Datenbankverbindung öffnen, weist Ihr Computer Speicher für diese Transaktion zu, und dieser Speicher wird nur beibehalten, wenn der Befehl disposed oder close ausgeführt wurde. Wenn jedoch während der Transaktion ein Fehler aufgetreten ist, wird der fortlaufende Befehl nur beendet, wenn er sich innerhalb des try.. finally..Blocks befindet.

catchwar anders als finallyin dem Sinne, dass catch so konzipiert war, dass Sie den Fehler selbst handhaben / verwalten oder interpretieren können. Stellen Sie sich das als eine Person vor, die Ihnen sagt: "Hey, ich habe ein paar Bösewichte erwischt. Was soll ich ihnen antun?" while finallywurde entwickelt, um sicherzustellen, dass Ihre Ressourcen ordnungsgemäß platziert wurden. Denken Sie an jemanden, der dafür sorgt, dass Ihr Eigentum noch sicher ist, unabhängig davon, ob es Bösewichte gibt oder nicht.

Und Sie sollten diesen beiden erlauben, für immer zusammenzuarbeiten.

beispielsweise:

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
quelle
1

Mit finally können Sie Ressourcen bereinigen, auch wenn Ihre catch-Anweisung die Ausnahme bis zum aufrufenden Programm auslöst. Bei Ihrem Beispiel mit der leeren catch-Anweisung gibt es kaum einen Unterschied. Wenn Sie jedoch in Ihrem Fang etwas verarbeiten und den Fehler auslösen oder gar keinen Fang haben, wird der Fehler immer noch ausgeführt.

Kibbee
quelle
1

Zum einen ist es eine schlechte Praxis, Ausnahmen zu fangen, mit denen Sie sich nicht befassen müssen. Lesen Sie Kapitel 5 über die .NET-Leistung, indem Sie die Leistung und Skalierbarkeit von .NET-Anwendungen verbessern . Nebenbei bemerkt, Sie sollten den Stream wahrscheinlich in den try-Block laden. Auf diese Weise können Sie die entsprechende Ausnahme abfangen, wenn sie fehlschlägt. Das Erstellen des Streams außerhalb des try-Blocks macht seinen Zweck zunichte.

Faktor Mystic
quelle
0

Unter wahrscheinlich vielen Gründen sind Ausnahmen sehr langsam auszuführen. Sie können Ihre Ausführungszeiten leicht verkürzen, wenn dies häufig vorkommt.

Lotsoffreetime
quelle
0

Das Problem mit Try / Catch-Blöcken, die alle Ausnahmen abfangen, besteht darin, dass sich Ihr Programm jetzt in einem unbestimmten Zustand befindet, wenn eine unbekannte Ausnahme auftritt. Dies verstößt vollständig gegen die Fail-Fast-Regel. Sie möchten nicht, dass Ihr Programm fortgesetzt wird, wenn eine Ausnahme auftritt. Der obige Versuch / Fang würde sogar OutOfMemoryExceptions abfangen, aber das ist definitiv ein Zustand, in dem Ihr Programm nicht ausgeführt wird.

Mit Try / finally-Blöcken können Sie Bereinigungscode ausführen, ohne dass dies schnell fehlschlägt. In den meisten Fällen möchten Sie nur alle Ausnahmen auf globaler Ebene abfangen, damit Sie sie protokollieren und dann beenden können.

David Mohundro
quelle
0

Der effektive Unterschied zwischen Ihren Beispielen ist vernachlässigbar, solange keine Ausnahmen ausgelöst werden.

Wenn jedoch in der 'try'-Klausel eine Ausnahme ausgelöst wird, wird sie im ersten Beispiel vollständig verschluckt. Das zweite Beispiel löst die Ausnahme für den nächsten Schritt im Aufrufstapel aus. Der Unterschied in den angegebenen Beispielen besteht darin, dass eine alle Ausnahmen vollständig verdeckt (erstes Beispiel) und die andere (zweites Beispiel) Ausnahmeinformationen für eine mögliche spätere Behandlung beibehält Der Inhalt in der Klausel 'finally' wird weiterhin ausgeführt.

Wenn Sie beispielsweise Code in die 'catch'-Klausel des ersten Beispiels einfügen, das eine Ausnahme auslöst (entweder die ursprünglich ausgelöste oder eine neue), wird der Reader-Bereinigungscode niemals ausgeführt. Wird schließlich ausgeführt, unabhängig davon, was in der 'catch'-Klausel passiert.

Der Hauptunterschied zwischen 'catch' und 'finally' besteht also darin, dass der Inhalt des Blocks 'finally' (mit wenigen seltenen Ausnahmen) auch bei unerwarteten Ausnahmen als garantiert ausgeführt werden kann, während Code folgt Eine 'catch'-Klausel (aber außerhalb einer' finally'-Klausel) würde eine solche Garantie nicht beinhalten.

Im Übrigen implementieren Stream und StreamReader beide IDisposable und können in einen 'using'-Block eingeschlossen werden. 'Using'-Blöcke sind das semantische Äquivalent von try / finally (kein' catch '), sodass Ihr Beispiel enger ausgedrückt werden könnte als:

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

... die die StreamReader-Instanz schließen und entsorgen, wenn sie den Gültigkeitsbereich verlässt. Hoffe das hilft.

Jared
quelle
0

try {…} catch {} ist nicht immer schlecht. Es ist kein allgemeines Muster, aber ich neige dazu, es zu verwenden, wenn ich Ressourcen herunterfahren muss, egal was passiert, wie das Schließen eines (möglicherweise) offenen Sockets am Ende eines Threads.

Martin Liesén
quelle