Warum kann sich eine 'continue'-Anweisung nicht in einem' finally'-Block befinden?

107

Ich habe kein Problem; Ich bin nur Neugierig. Stellen Sie sich folgendes Szenario vor:

foreach (var foo in list)
{
    try
    {
         //Some code
    }
    catch (Exception)
    {
        //Some more code
    }
    finally
    {
        continue;
    }
}

Dies wird nicht kompiliert, da es den Compilerfehler CS0157 auslöst :

Die Kontrolle kann den Text einer finally-Klausel nicht verlassen

Warum?

lpaloub
quelle
7
So. Nur neugierig. Wenn es für Sie völlig sinnvoll ist, warum es nicht kompiliert wird, warum soll jemand erklären, was bereits Sinn macht? =)
J. Steen
14
warum brauchst du einen continue;in finallyblock? ist es nicht dasselbe wie continue;nach dem try -- catchBlock?
Bansi
6
@ J.Steen Nein, ich weiß, dass dies finally/continueeine Einschränkung des C # -Compilers ist :-) Ich bin auch neugierig auf den Grund dieser Einschränkung.
Xanatos
5
@xanatos - Technisch gesehen ist dies eine Einschränkung der zugrunde liegenden CIL. Aus der Sprachspezifikation : "Die Steuerübertragung darf niemals einen Catch-Handler oder eine finally-Klausel eingeben, außer über den Ausnahmebehandlungsmechanismus." und "Die Übertragung von Steuerelementen aus einem geschützten Bereich ist nur durch eine Ausnahmeanweisung (Leave, Endfilter, Endcatch oder End.Final) zulässig." Die brFamilie der Zweiganweisungen kann dies nicht erreichen.
Unsigned
1
@Unsigned: Es ist auch eine gute Einschränkung, so dass es bizarr wäre :)
user7116

Antworten:

150

finallyBlöcke werden ausgeführt, unabhängig davon, ob eine Ausnahme ausgelöst wird oder nicht. Was zum Teufel würde continuetun, wenn eine Ausnahme ausgelöst wird ? Sie können die Ausführung der Schleife nicht fortsetzen, da eine nicht erfasste Ausnahme die Steuerung an eine andere Funktion überträgt.

Selbst wenn keine Ausnahme ausgelöst wird, finallywird sie ausgeführt, wenn andere Steuerungsübertragungsanweisungen innerhalb des try / catch-Blocks ausgeführt werden return, z. B. a, was das gleiche Problem mit sich bringt.

Kurz gesagt, mit der Semantik macht finallyes keinen Sinn, die Kontrolle von innerhalb eines finallyBlocks nach außen zu übertragen.

Dies mit einer alternativen Semantik zu unterstützen, wäre eher verwirrend als hilfreich, da es einfache Problemumgehungen gibt, die das beabsichtigte Verhalten klarer machen. Sie erhalten also einen Fehler und müssen über Ihr Problem richtig nachdenken. Es ist die allgemeine Idee "Wirf dich in die Grube des Erfolgs", die in C # weitergeht.

C #, Sie und das Out, wenn Erfolg

Wenn Sie Ausnahmen ignorieren möchten (meistens ist dies eine schlechte Idee) und die Schleife weiter ausführen möchten, verwenden Sie einen catch all-Block:

foreach ( var in list )
{
    try{
        //some code
    }catch{
        continue;
    }
}

Wenn Sie continuenur dann möchten, wenn keine nicht erfassten Ausnahmen ausgelöst werden, setzen Sie sie einfach continueaußerhalb des Try-Blocks.

R. Martinho Fernandes
quelle
12
Ich werde dies als Antwort akzeptieren, da Sie die Gründe verstehen, warum Microsoft möglicherweise beschlossen hat, eine Fortsetzung in einer endgültigen Version nicht zu akzeptieren. Wahrscheinlich hat mich dieses Bild auch überzeugt :)
lpaloub
20
Können Sie die Idee "Wirf dich in die Grube des Erfolgs" ausarbeiten? Ich habe es nicht verstanden :-D
Ant
13
Das Bild wurde übrigens von Jon Skeet gemacht. Ich habe von hier: msmvps.com/blogs/jon_skeet/archive/2010/09/02/…
R. Martinho Fernandes
1
Natürlich ist a continuein einem finallyBlock in Ordnung, wenn nur eine im finallyBlock definierte lokale Schleife fortgesetzt wird . In der Frage wird jedoch versucht, eine "äußere" Schleife fortzusetzen. Ähnliche Anweisungen, die Sie nicht in einem finallyBlock haben können return, sind break(wenn Sie aus dem Block ausbrechen) und goto(wenn Sie zu einem Label außerhalb des finallyBlocks gehen). Eine verwandte Java-Diskussion finden Sie unter Zurückkehren von einem finally-Block in Java .
Jeppe Stig Nielsen
32

Hier ist eine zuverlässige Quelle:

Eine continue-Anweisung kann einen finally-Block nicht beenden (Abschnitt 8.10). Wenn eine continue-Anweisung innerhalb eines finally-Blocks auftritt, muss sich das Ziel der continue-Anweisung innerhalb desselben finally-Blocks befinden. Andernfalls tritt ein Fehler bei der Kompilierung auf.

Es stammt aus MSDN, 8.9.2 Die continue-Anweisung .

Die Dokumentation sagt, dass:

Die Anweisungen eines finally-Blocks werden immer ausgeführt, wenn die Steuerung eine try-Anweisung hinterlässt. Dies gilt unabhängig davon, ob die Steuerungsübertragung als Ergebnis einer normalen Ausführung, als Ergebnis der Ausführung einer Anweisung break, continue, goto oder return oder als Ergebnis der Weitergabe einer Ausnahme aus der try-Anweisung erfolgt. Wenn während der Ausführung eines finally-Blocks eine Ausnahme ausgelöst wird, wird die Ausnahme an die nächste umschließende try-Anweisung weitergegeben. Wenn eine andere Ausnahme gerade weitergegeben wird, geht diese Ausnahme verloren. Der Prozess der Weitergabe einer Ausnahme wird in der Beschreibung der throw-Anweisung (Abschnitt 8.9.5) näher erläutert.

Es ist von hier 8.10 Die try-Anweisung .

Mortalus
quelle
31

Sie mögen denken, dass es Sinn macht, aber es macht eigentlich keinen Sinn .

foreach (var v in List)
{
    try
    {
        //Some code
    }
    catch (Exception)
    {
        //Some more code
        break; or return;
    }
    finally
    {
        continue;
    }
}

Was beabsichtigen Sie, eine Pause einzulegen oder fortzufahren, wenn eine Ausnahme ausgelöst wird? Das C # -Compilerteam möchte keine eigene Entscheidung treffen, indem es breakoder annimmt continue. Stattdessen beschlossen sie, sich darüber zu beschweren, dass die Entwicklersituation nicht eindeutig ist, um die Kontrolle zu übertragen finally block.

Es ist also die Aufgabe des Entwicklers, klar anzugeben, was er vorhat, anstatt dass der Compiler etwas anderes annimmt.

Ich hoffe du verstehst warum das nicht kompiliert wird!

Sriram Sakthivel
quelle
Selbst wenn es keinen "catch" finallygäbe, würde der Ausführungspfad nach einer Anweisung davon abhängen, ob der catchüber eine Ausnahme beendet wurde, und es gibt keinen Mechanismus, um zu sagen, wie dies mit a interagieren soll continue. Ich mag Ihr Beispiel jedoch, weil es ein noch größeres Problem zeigt.
Supercat
@supercat Stimmen Sie zu, meine Antwort zeigt ein Beispiel für eine mehrdeutige Situation für den Compiler, dennoch gibt es so viele Probleme mit diesem Ansatz.
Sriram Sakthivel
16

Wie andere gesagt haben, sich aber auf Ausnahmen konzentrieren, geht es wirklich um die mehrdeutige Behandlung der Übertragung von Kontrolle.

In Ihrem Kopf denken Sie wahrscheinlich an ein Szenario wie dieses:

public static object SafeMethod()
{
    foreach(var item in list)
    {
        try
        {
            try
            {
                //do something that won't transfer control outside
            }
            catch
            {
                //catch everything to not throw exceptions
            }
        }
        finally
        {
            if (someCondition)
                //no exception will be thrown, 
                //so theoretically this could work
                continue;
        }
    }

    return someValue;
}

Theoretisch können Sie den Kontrollfluss verfolgen und sagen, ja, das ist "ok". Es wird keine Ausnahme ausgelöst, keine Kontrolle übertragen. Die C # -Sprachendesigner hatten jedoch andere Probleme.

Die geworfene Ausnahme

public static void Exception()
{
    try
    {
        foreach(var item in list)
        {
            try
            {
                throw new Exception("What now?");
            }
            finally
            {
                continue;
            }
        }
    }
    catch
    {
        //do I get hit?
    }
}

Der gefürchtete Goto

public static void Goto()
{
    foreach(var item in list)
    {
        try
        {
            goto pigsfly;
        }
        finally
        {
            continue;
        }
    }

    pigsfly:
}

Die Rückkehr

public static object ReturnSomething()
{
    foreach(var item in list)
    {
        try
        {
            return item;
        }
        finally
        {
            continue;
        }
    }
}

Die Trennung

public static void Break()
{
    foreach(var item in list)
    {
        try
        {
            break;
        }
        finally
        {
            continue;
        }
    }
}

Zusammenfassend lässt sich sagen, dass es zwar eine geringfügige Möglichkeit gibt, a continuein Situationen zu verwenden, in denen die Kontrolle nicht übertragen wird, aber in vielen Fällen (Mehrheit?) Ausnahmen oder returnBlockierungen auftreten. Die Sprachdesigner waren der Meinung, dass dies zu vieldeutig und (wahrscheinlich) unmöglich wäre, um bei der Kompilierung sicherzustellen, dass Ihre continueverwendet wird nur in Fällen , in denen der Kontrollfluss nicht übertragen wird.

Chris Sinclair
quelle
Ich habe die gleiche Situation in Java, als der Proguard während der Verschleierung abstürzte. Ihre Erklärung ist ziemlich gut. C # gibt einen Fehler bei der Kompilierung - macht durchaus Sinn.
Dev_Vikram
11

Im Allgemeinen continuemacht es keinen Sinn, wenn es im finallyBlock verwendet wird. Schau dir das an:

foreach (var item in list)
{
    try
    {
        throw new Exception();
    }
    finally{
        //doesn't make sense as we are after exception
        continue;
    }
}
Ghord
quelle
"Im Allgemeinen" machen viele Dinge keinen Sinn. Und das heißt nicht, dass es nicht "besonders" funktionieren sollte. IE: continuemacht außerhalb der Schleifenanweisung keinen Sinn, bedeutet aber nicht, dass sie nicht unterstützt wird.
Zerkms
Ich denke, es macht Sinn. itemkann Datei sein, Lesen fehlgeschlagen -> finallyschließt die Datei. continueverhindert den Rest der Verarbeitung.
Jnovacho
5

"Das wird nicht kompiliert und ich denke, es macht durchaus Sinn."

Nun, ich denke nicht.

Wenn Sie buchstäblich haben, catch(Exception)dann brauchen Sie nicht das endgültige (und wahrscheinlich nicht einmal das continue).

catch(SomeException)Was sollte passieren, wenn eine Ausnahme nicht abgefangen wird, wenn Sie realistischer sind? Sie continuemöchten einen Weg gehen, die Ausnahme einen anderen.

Henk Holterman
quelle
2
Ich denke, Sie könnten brauchen, finallywenn Sie haben catch. Es wird häufig zum zuverlässigen Schließen von Ressourcen verwendet.
Jnovacho
Es sind jedoch nicht nur Ausnahmen. Sie könnten leicht eine returnAussage in Ihrem tryoder catchBlöcken haben. Was tun wir jetzt? Zurückkehren oder die Schleife fortsetzen? (Und wenn wir fortfahren, was ist, wenn es das letzte Element ist, das iteriert? Dann fahren wir einfach außerhalb von fort foreachund die Rückgabe erfolgt nie?) BEARBEITEN: Oder sogar gotozu einem Label außerhalb der Schleife, so ziemlich jeder Aktion, die die Kontrolle außerhalb der foreachSchleife überträgt .
Chris Sinclair
2
Ich bin nicht einverstanden mit "Wenn Sie buchstäblich Fang haben (Ausnahme), dann brauchen Sie das Endlich (und wahrscheinlich nicht einmal das Weiter) nicht." Was ist, wenn ich eine Operation ausführen muss, unabhängig davon, ob eine Ausnahme ausgelöst wurde oder nicht?
lpaloub
OK, das sorgt schließlich für zusätzliche returns innerhalb des Blocks. Normalerweise wird die Ausführung jedoch nach dem Allheilmittel fortgesetzt.
Henk Holterman
"endlich" ist nicht "fangen". Es sollte für den Bereinigungscode verwendet werden. Ein finally-Block wird ausgeführt, unabhängig davon, ob eine Ausnahme ausgelöst wird oder nicht . Dinge wie das Schließen von Dateien oder das Freigeben von Speicher (wenn Sie nicht verwalteten Code verwenden) usw. Dies sind die Dinge, die Sie in einen endgültigen Block setzen
Robotnik
3

Sie können den Körper eines endgültigen Blocks nicht verlassen. Dies umfasst die Schlüsselwörter break, return und in Ihrem Fall continue.

Joseph Devlin
quelle
3

Der finallyBlock kann mit einer Ausnahme ausgeführt werden, die darauf wartet, erneut ausgelöst zu werden. Es wäre nicht wirklich sinnvoll, den Block verlassen zu können (durch acontinue oder irgendetwas anderes) verlassen zu können, ohne die Ausnahme erneut auszulösen.

Wenn Sie Ihre Schleife fortsetzen möchten, brauchen Sie nicht die finally-Anweisung: Fangen Sie einfach die Ausnahme ab und werfen Sie sie nicht erneut.

Falanwe
quelle
1

finallyLäuft, ob eine nicht erfasste Ausnahme ausgelöst wird oder nicht. Andere haben bereits erklärt, warum dies continueunlogisch ist, aber hier ist eine Alternative, die dem Geist dessen folgt, wonach dieser Code zu fragen scheint. Grundsätzlich finally { continue; }heißt es:

  1. Wenn es Ausnahmen gibt, fahren Sie fort
  2. Wenn es nicht erfasste Ausnahmen gibt, lassen Sie sie werfen, aber fahren Sie fort

(1) könnte durch Platzieren continueam Ende eines jeden erfüllt werden catch, und (2) könnte durch Speichern nicht erfasster Ausnahmen, die später geworfen werden sollen, befriedigt werden. Sie könnten es so schreiben:

var exceptions = new List<Exception>();
foreach (var foo in list) {
    try {
        // some code
    } catch (InvalidOperationException ex) {
        // handle specific exception
        continue;
    } catch (Exception ex) {
        exceptions.Add(ex);
        continue;
    }
    // some more code
}
if (exceptions.Any()) {
    throw new AggregateException(exceptions);
}

Eigentlich finallyhätte auch im dritten Fall ausgeführt, wo es überhaupt keine Ausnahmen gab, gefangen oder nicht gefangen. Wenn das gewünscht wäre, könnten Sie natürlich einfach einen einzelnen continuenach dem Try-Catch-Block platzieren, anstatt in jedem catch.

nmclean
quelle
1

Technisch gesehen ist dies eine Einschränkung der zugrunde liegenden CIL. Aus der Sprachspezifikation :

Die Steuerübertragung darf nur über den Ausnahmebehandlungsmechanismus in einen Catch-Handler oder eine finally-Klausel eingegeben werden.

und

Steuertransfer aus einem geschützten Bereich ist nur durch eine Ausnahmebefehl erlaubt ( leave, end.filter, end.catch, oder end.finally)

Auf der Dokumentseite für die brAnweisung :

Steuerungsübertragungen in und aus Try-, Catch-, Filter- und schließlich-Blöcken können mit dieser Anweisung nicht ausgeführt werden.

Dieser letzte gilt für wahr alle Zweigbefehle, einschließlich beq, brfalseusw.

Ohne Vorzeichen
quelle
-1

Die Designer der Sprache wollten (oder konnten) einfach nicht begründen, dass die Semantik eines endgültigen Blocks durch eine Steuerungsübertragung beendet wird.

Ein Problem oder vielleicht das Hauptproblem ist, dass die finally Block als Teil einer nicht lokalen Steuerübertragung (Ausnahmeverarbeitung) ausgeführt wird. Das Ziel dieser Steuerübertragung ist nicht die umschließende Schleife. Die Ausnahmeverarbeitung bricht die Schleife ab und wickelt sich weiter ab.

Wenn wir eine Kontrollübertragung aus dem haben finally Bereinigungsblock haben, wird die ursprüngliche Kontrollübertragung "entführt". Es wird abgebrochen und die Kontrolle geht woanders hin.

Die Semantik kann ausgearbeitet werden. Andere Sprachen haben es.

Die Designer von C # haben beschlossen, statische, "goto-ähnliche" Steuerübertragungen einfach nicht zuzulassen, wodurch die Dinge etwas vereinfacht wurden.

Selbst wenn Sie dies tun, wird die Frage, was passiert, wenn eine dynamische Übertragung von a initiiert wird, nicht gelöst finally : Was wenn der finally-Block eine Funktion aufruft und diese Funktion auslöst? Die ursprüngliche Ausnahmeverarbeitung wird dann "entführt".

Wenn Sie die Semantik dieser zweiten Form der Entführung herausarbeiten, gibt es keinen Grund, den ersten Typ zu verbannen. Sie sind wirklich dasselbe: Eine Kontrollübertragung ist eine Kontrollübertragung, unabhängig davon, ob sie denselben lexikalischen Umfang hat oder nicht.

Kaz
quelle
Der Unterschied ist , dass finallyper Definition muss sofort durch die Fortsetzung der Übertragung folgen, die es ausgelöst (eine abgefangene Ausnahme, returnusw.). Es wäre leicht, "goto-ähnliche" Übertragungen innerhalb von zuzulassen finally(welche Sprachen tun dies übrigens?), Aber dies würde bedeuten, dass dies try { return } finally { ... } möglicherweise nicht zurückkehrt , was völlig unerwartet ist. Wenn finallyeine Funktion aufgerufen wird, die ausgelöst wird, ist dies nicht wirklich dasselbe, da wir bereits erwarten, dass jederzeit Ausnahmen auftreten und den normalen Ablauf unterbrechen können.
nmclean
@nmclean: Tatsächlich ist eine Ausnahme, die entgeht, finallyinsofern dasselbe, als sie zu einem unerwarteten und unlogischen Programmfluss führen kann. Der einzige Unterschied besteht darin, dass die Sprachdesigner nicht in der Lage sind, alle Programme abzulehnen, bei denen ein endgültiger Block eine nicht behandelte Ausnahme auslösen könnte, sondern stattdessen zulassen, dass solche Programme kompiliert werden, und hoffen, dass das Programm alle Konsequenzen akzeptieren kann, die sich aus dem Abbruch der früheren Ausnahmefreigabe ergeben können Reihenfolge.
Supercat
@supercat Ich bin damit einverstanden, dass der erwartete Fluss unterbrochen wird, aber mein Punkt ist, dass dies bei nicht behandelten Ausnahmen immer der Fall ist, unabhängig davon, ob der aktuelle Fluss ein ist finallyoder nicht. Nach der Logik des letzten Absatzes von Kaz sollten Funktionen auch frei sein, den Fluss im Bereich ihres Anrufers über continueusw. zu beeinflussen, da sie dies aus Ausnahmen tun dürfen. Dies ist aber natürlich nicht erlaubt; Ausnahmen sind die einzige Ausnahme von dieser Regel, daher der Name "Ausnahme" (oder "Interrupt").
nmclean
@nmclean: Nicht verschachtelte Ausnahmen stellen einen Kontrollfluss dar, der sich von der normalen Ausführung unterscheidet, aber dennoch strukturiert ist. Die Anweisung nach einem Block sollte nur ausgeführt werden, wenn alle innerhalb dieses Blocks aufgetretenen Ausnahmen darin abgefangen wurden. Das mag vernünftig erscheinen, aber eine Ausnahme, die innerhalb eines finallyBlocks auftritt , kann dagegen verstoßen. Wirklich eine schlimme Situation, die meiner Meinung nach hätte gelöst werden müssen, indem finallyBlöcke mindestens über ausstehende Ausnahmen informiert wurden, damit sie zusammengesetzte Ausnahmeobjekte erstellen können.
Supercat
@mmclean Entschuldigung, ich stimme nicht zu, dass der Name "Ausnahme" von einer "Ausnahme von der Regel" (in Bezug auf die Steuerung) stammt, die für C #, LOL spezifisch ist. Der Aspekt der Änderung des Steuerflusses einer Ausnahme ist einfach eine nicht lokale dynamische Steuerübertragung. Eine regelmäßige Kontrollübertragung innerhalb desselben lexikalischen Rahmens (kein Abwickeln) kann als Sonderfall angesehen werden.
Kaz