2017-02-15 2 views
2

Ich dachte, ich hätte verstanden, wie die Ausnahmebehandlung in C# funktioniert. Re-Lesen der Dokumentation für Spaß und Selbstvertrauen, habe ich auf Probleme stoßen:Ausnahmebehandlung (widersprüchliche Dokumentation/try-finally vs. Verwendung)

This document behauptet, dass die folgenden zwei Code-Schnipsel gleichwertig sind, noch mehr, dass die erste in die letztere zum Kompilieren übersetzt wird.

using (Font font1 = new Font("Arial", 10.0f)) { 
    byte charset = font1.GdiCharSet; 
} 

und

{ 
    Font font1 = new Font("Arial", 10.0f); 
    try { 
    byte charset = font1.GdiCharSet; 
    } 
    finally { 
    if (font1 != null) 
     ((IDisposable)font1).Dispose(); 
    } 
} 

Darüber hinaus ist es behauptet:

Die using-Anweisung stellt sicher, dass Entsorgen auch genannt wird, wenn eine Ausnahme auftritt, während Sie Methoden für das Objekt aufrufen.

Im Gegensatz dazu that document heißt es:

Innerhalb einer behandelten Ausnahme der zugehörige finally Block garantiert ausgeführt werden soll. Wenn die Ausnahme jedoch nicht behandelt wird, hängt die Ausführung des Blocks finally davon ab, wie die Ausnahmeabwicklungsoperation ausgelöst wird.

Ich bekomme das nicht zusammen. Im Codebeispiel aus dem ersten Dokument ist die Ausnahme eindeutig nicht behandelt (da es keinen Catch-Block gibt). Wenn nun die Aussage aus dem zweiten Dokument wahr ist, ist der finally Block nicht garantiert auszuführen. Dies widerspricht letztlich dem, was das erste Dokument sagt ("Die using Erklärung sichert ...") (Hervorhebung von mir).

Also was ist die Wahrheit?

EDIT 1

ich es immer noch nicht. Die Antwort von StevieB hat mich dazu gebracht, mehr Teile aus der C# -Sprachspezifikation zu lesen. In Abschnitt 16.3 haben wir:

[...] Diese Suche wird fortgesetzt, bis eine Klausel Haken ist, dass die aktuelle Ausnahme behandeln kann gefunden [...] Sobald Klausel eine passende Raste gefunden, das System bereitet vor, die Kontrolle auf die erste Anweisung der Fangklausel zu übertragen. Bevor die Abarbeitung der catch-Klausel beginnt, führt das System zuerst in der Reihenfolge alle finally-Klauseln aus, die mit try-Anweisungen verschachtelten, die verschachtelter waren als die -Ausnahme.

So habe ich ein einfaches Testprogramm aus, das Code enthält, die von Null eine Division produziert und ist innerhalb eines try Block.Diese Ausnahme ist nie in irgendwelchen meiner Code gefangen, aber die jeweiligen try Anweisung hat einen finally Block:

int b = 0; 

try { 
    int a = 10/b; 
} 
finally { 
    MessageBox.Show("Hello"); 
} 

zunächst entsprechend der Dokumentation Snippet oben, ich hatte erwartet, dass die finally Block nie ausgeführt werden würde, und dass Das Programm würde einfach sterben, wenn es ohne angehängten Debugger ausgeführt würde. Aber das ist nicht der Fall; stattdessen wird das "Ausnahme-Dialogfeld", das wir alle zu gut kennen, angezeigt, und danach erscheint das Dialogfeld "Hello".

Nach einer Weile darüber nachgedacht und nach mit Lese docs, Artikel und Fragen wie this und that, wurde klar, dass dieses „Ausnahme Dialogfeld“ durch einen Standard-Exception-Handler erzeugt wird, die in Application.Run gebaut wird() und die anderen üblichen Methoden, die Ihr Programm "starten" könnten, so wundere ich mich nicht mehr warum der finally Block ausgeführt wird.

Aber ich bin immer noch völlig verwirrt, weil der "Hallo" -Dialog erscheint nach die "Ausnahmedialogfeld". Das obige Dokumentations-Snippet ist ziemlich klar (naja, wahrscheinlich bin ich wieder einfach zu albern):

Die CLR findet keine catch Klausel, die mit der try Anweisung verbunden ist, wo die Division durch Null geschieht. Also sollte es die Ausnahme eine Ebene höher an den Aufrufer übergeben, wird dort auch keine passende catch-Klausel finden (dort gibt es nicht einmal eine try Aussage) und so weiter (wie oben erwähnt, handle ich nicht (dh fange) jede Ausnahme in diesem Testprogramm).

Schließlich sollte die Ausnahme die CLR-Standard Catch-All-Ausnahmebehandlung (dh diejenige, die standardmäßig in Application.Run() und seinen Freunden aktiv ist), aber (laut der oben genannten Dokumentation) sollte die CLR jetzt Führe alle finally Blöcke aus, die tiefer verschachtelt sind als dieser Standard-Handler ("mein" finally Block gehört zu diesen, nicht wahr?) vor Ausführung der CLR-Catch-All-Standard-Handler catch Block.

Das bedeutet, dass der "Hallo" -Dialog sollte vor die "Ausnahmedialogfeld" angezeigt werden, nicht wahr ?. Nun, offensichtlich ist es umgekehrt. Könnte jemand das näher ausführen?

+0

Es ist ein kleines bisschen unklar, was Sie fragen. Fragen Sie im Grunde, ob ein 'using' zu 100% disponieren wird? Wenn ja, hast du Recht, nein, es wird nicht (notwendigerweise), da es der Art von Ausnahme unterliegt, die während des Codes, den es enthält, ausgelöst wurde, ähnlich dem "Versuch fangen", du bist vielleicht vertrauter mit. –

+1

Ich glaube, dass "unbehandelt" bedeutet, dass die Ausnahme von der Anwendung nicht behandelt wird, d. H. Die Ausnahme führt dazu, dass die Anwendung beendet wird/abstürzt. Da die Anwendung beendet wird, ist die Ausführung des finally-Blocks nicht unbedingt erforderlich. – StevieB

+0

@ JᴀʏMᴇᴇ: Ich denke, er fragt, ob es möglich ist, dass ein 'finally' nicht auf unbehandelten Ausnahmen ausgeführt wird oder was _exception unwind operation_ bedeutet. Der Link [Unbehandelte Ausnahmeverarbeitung in der CLR] (http://go.microsoft.com/fwlink/?LinkId=128371) in [MSDN] (https://msdn.microsoft.com/en-us/library/zwc8s4fz .aspx) ist rot –

Antwort

-1

Zunächst möchte ich Sie darauf hinweisen, dass mit Anweisung nicht für alle Arten verwendet werden kann. Dies kann nur für Typen verwendet werden, die die Schnittstelle IDisposable implementieren, die über eine Funktionalität verfügt, das Objekt automatisch zu entsorgen. Dies ist in dem zweiten Dokument vorhanden, das Sie erwähnten

C# enthält auch die using-Anweisung, die ähnliche Funktionalität für IDisposable-Objekte in einer praktischen Syntax bietet.

Das bedeutet, wenn eine nicht behandelte Ausnahme geschieht, bis die Objekt Reinigung durch die Methode Dispose() des Typs gehandhabt wird (dies für die Verwendung von Anweisung Dokumentation gegeben)

auf die Anfrage kommt, auch wenn Es wird nicht garantiert, dass Ihr finally block (generated) für unbehandelte Exceptions ausgeführt wird.Net CLR

Hope this löscht Ihre Zweifel

+4

Ich kann nicht sehen, wie dies die Frage beantwortet – apomene

+1

Interessanter Aspekt, aber es beantwortet meine Frage nicht. – Binarus

+0

Ich würde gerne wissen, welcher Teil der OP-Frage 'using' falsch verwendet – MickyD

0

Wenn Ihre Definition von „nicht behandelte Ausnahme“ (dass es durch eine catch Klausel im gleichentry Block behandelt werden müssen) richtig war, es gäbe keinen Grund, jemals dem Konstrukt

try { 
    ... 
} 
finally { 
    ... 
} 

Da von Ihrer Definition erlauben, der finally Block würde nie ausgeführt. Da das obige Konstrukt gültig ist, müssen wir daraus schließen, dass Ihre Definition von "nicht behandelte Ausnahme" falsch ist.

Was bedeutet es, "wenn die Ausnahme nicht von beliebigen Exception-Handler, irgendwo im Call-Stack behandelt wird".

+0

Sie haben Recht, in diesem Zusammenhang habe ich es so verstanden: Eine unbehandelte Ausnahme ist eine Ausnahme, die nicht durch den 'catch' Block * gefangen wird gehört zu dem jeweiligen 'try' Block *. Offensichtlich habe ich den Begriff "unbehandelt" falsch interpretiert (ich war mir bewusst, dass ich bei allen Anrufern, die den Stapel hochkamen, Ausnahmen abfangen konnte, nahm aber nicht an, dass dies in diesem Zusammenhang wichtig war). – Binarus

2

Dieses Dokument behauptet, dass die beiden folgenden Codefragmente äquivalent sind

Sie sind.

Die using-Anweisung stellt sicher, dass Dispose aufgerufen wird, auch wenn eine Ausnahme auftritt, während Sie Methoden für das Objekt aufrufen.

Ziemlich viel.

Dies widerspricht letztlich, was das erste Dokument sagt

Nun, die ersten etwas zu vage wurde, anstatt flach-out falsch.

Es gibt Fälle, in denen eine finally nicht ausgeführt wird, einschließlich der durch eine using impliziert. Ein StackOverflowException wäre ein Beispiel (ein echtes vom Überlaufen des Stacks, wenn Sie nur throw new StackOverflowException() das endlich laufen lassen).

Alle Beispiele sind Dinge, die Sie nicht fangen können, und Ihre Anwendung geht unter, wenn also die Reinigung von using nur wichtig ist, während die Anwendung läuft, dann ist finally in Ordnung.

Wenn die Bereinigung wichtig ist, selbst wenn das Programm abstürzt, dann kann finally nie genug sein, da es z. Ein Stromstecker, der herausgezogen wird, was in den Fällen, in denen die Reinigung selbst bei einem Unfall lebenswichtig ist, ist ein Fall, der in Betracht gezogen werden muss.

In jedem Fall, in dem die Ausnahme weiter aufgefangen wird und das Programm fortgesetzt wird, wird finally ausgeführt.

Mit abfangbaren Ausnahmen, die nicht abgefangen werden, werden normalerweise finally Blöcke ausgeführt, aber es gibt noch einige Ausnahmen. Einer wäre, wenn der try - finally innerhalb eines Finaliser war und der try lange dauerte; Nach einer Weile auf der Finaliser-Warteschlange wird die Anwendung nur schnell ausfallen.

0

Ich glaube, (korrigiert mich wenn ich falsch liege) die Antwort in der Definition liegt:

Die using-Anweisung stellt sicher, dass Entsorgen auch genannt wird, wenn ein Ausnahme auftritt , während Sie das Aufrufen von Methoden auf die Objekt.

Wenn also irgendwelche Ausnahmen in irgendwelchen vom Objekt aufgerufenen Methoden auftreten, ist schließlich sichergestellt, dass sie ausgeführt werden. Auf der anderen Seite kann nicht garantiert werden, dass innerhalb des Aufrufs des Aufrufblocks andere Methoden ausgeführt werden, die nicht mit dem Objekt zusammenhängen.

+0

Danke, dass Sie uns diese Definition mitgeteilt haben, aber ich denke, dass die Antwort nicht darin liegt. Laut den anderen Antworten und was ich zuvor gelesen habe, hängt es davon ab, ob die Ausnahme in * irgendeinem der Anrufer * auf dem Stapel abgehandelt wird. – Binarus

+0

Die C# * Spezifikation * enthält keine Formulierung wie diese. Ich vermute, dass dies ein weiteres Beispiel für die Autoren von C# * Reference * ist, die versuchen, einem Punkt mehr Erklärung zu geben und stattdessen Verwirrung zu stiften. –

0

Die C# -Sprachspezifikation besagt, dass die finally-Blöcke ausgeführt werden (sei es in einer using-Anweisung oder an anderer Stelle) für System.Exception oder eine der abgeleiteten Ausnahmen. Natürlich, wenn Sie eine Ausnahme bekommen, die nicht mit der üblichen try..catch Logik behandelt werden kann, z. Access alle Wetten ab, die ist, wo die Mehrdeutigkeit kommt

Die Spezifikation mit Visual Studio 2013 und höher installiert ist -. Mit 2017 ist es in C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC#\Specifications\1033

In Abschnitt

8.9.5 Der Wurf

Anweisung

sehen wir folgendes:

Wenn eine Ausnahme ausgelöst wird, wird die Kontrolle an die erste catch-Klausel in einer umschließenden try-Anweisung übergeben, die die Ausnahme verarbeiten kann. Der Prozess, der ab dem Punkt stattfindet, bei dem die Ausnahme an den Punkt übertragen wird, an dem die Kontrolle an einen geeigneten Ausnahmebehandler übergeben wird, wird als Ausnahmeverbreitung bezeichnet. Die Propagierung einer Ausnahme besteht darin, die folgenden Schritte wiederholt zu bewerten, bis eine catch-Klausel gefunden wird, die der Ausnahme entspricht: . In dieser Beschreibung ist der Wurfpunkt anfänglich der Ort , bei dem die Ausnahme ausgelöst wird.

  • In der aktuellen Funktion member, wird jede try-Anweisung, die den throw-Punkt einschließt, untersucht. Für jede Anweisung S, mit der innersten try-Anweisung beginnt und mit die äußersten try-Anweisung endete, werden die folgenden Schritte bewertet:
    • Wenn der try-Block von S den Ball Punkt umschließt und wenn S eine oder mehr catch-Klauseln, die catch-Klauseln werden in der Reihenfolge des Auftretens untersucht, um einen geeigneten Handler für die Ausnahme zu finden. Die erste catch-Klausel , die den Ausnahmetyp oder einen Basistyp des Ausnahmetyps angibt, wird als Übereinstimmung betrachtet. Eine allgemeine Fangklausel (§8.10) wird als Übereinstimmung für jeden Ausnahmetyp angesehen. Wenn eine übereinstimmende Catch-Klausel gefunden wird, wird die Ausnahmeausbreitung abgeschlossen, indem die Steuerung an den Block dieser Catch-Klausel übergeben wird.
    • Andernfalls, wenn der Versuch Block oder ein Catch Block von S umschließt den Wurfpunkt und wenn S hat einen endgültigen Block, Kontrolle wird auf den endgültigen Block übertragen. Wenn der finally-Block eine weitere Ausnahme auslöst, wird die Verarbeitung der aktuellen Ausnahme beendet. Andernfalls, wenn die Steuerung den Endpunkt des Blocks finally erreicht, wird die Verarbeitung der aktuellen Ausnahme fortgesetzt.
  • Wenn einen Ausnahmebehandler nicht in der aktuellen Funktion Invokation angeordnet wurde, wird die Funktion Aufruf beendet wird, und eine der folgenden Ereignisse eintritt:
    • Wenn die aktuelle Funktion ist nicht-async, die Die obigen Schritte werden für den Aufrufer der Funktion mit einem Auswurfpunkt wiederholt, der der Anweisung entspricht, von der das Funktionsmember aufgerufen wurde.
    • Wenn die aktuelle Funktion asynchron ist und die Aufgabe zurückkehrt, wird die Ausnahme in der Rückgabetask aufgezeichnet, die wie in Abschnitt 10.14.1 beschrieben in einen fehlerhaften oder abgebrochenen Status versetzt wird.
    • Wenn die aktuelle Funktion async und void-returning ist, wird der Synchronisationskontext des aktuellen Threads wie in §10.14.2 beschrieben gemeldet.
  • Wenn die Ausnahmeverarbeitung alle Funktionselemente Invokationen im aktuellen Thread beendet wird, was darauf hinweist, dass der Faden für die Ausnahme keinen Handler hat, dann wird der Faden selbst beendet. Die Auswirkung einer solchen Terminierung ist implementationsdefiniert.
  • +1

    Das ist eine große Mauer mit zitiertem Text, die man einwerfen könnte - vielleicht, wenn Sie ** die wichtigsten Textstellen, die Sie hier für sachdienlich halten, hervorheben? –

    0

    Sie müssen der Dokumentation ein wenig Spielraum geben. In den meisten Fällen gibt es einen impliziten "innerhalb des Grundes". Wenn ich zum Beispiel meinen Computer ausschalte, wird keiner dieser Blöcke blockiert oder/Disposes wird aufgerufen.

    Außer dem Herunterfahren des Computers gibt es eine Handvoll Umstände, unter denen OS Ihre Anwendung beenden kann - und damit praktisch ausgeschaltet wird. Unter diesen Umständen werden Dispose und finally nicht aufgerufen. Das sind normalerweise ziemlich schwerwiegende Fehlerbedingungen wie aus dem Stapel oder nicht genügend Speicher. Es gibt einige komplexe Szenarien, in denen dies mit weniger als Ausnahme Ausnahmen passieren kann. Wenn Sie beispielsweise über systemeigenen Code verfügen, der ein verwaltetes Objekt in einem Hintergrundthread erstellt und das verwaltete Objekt und etwas in diesem Thread eine systemeigene Ausnahme auslöst, werden die verwalteten Ausnahmehandler wahrscheinlich nicht vom Betriebssystem aufgerufen (z. B. Dispose) beendet nur den Thread und alles, was entsorgt werden könnte, ist nicht mehr zugänglich.

    Aber diese Anweisungen sind tatsächlich gleichwertig und innerhalb finally Block wird ausgeführt und Dispose wird aufgerufen.