Nicht erreichbarer Code, aber mit einer Ausnahme erreichbar


108

Dieser Code ist Teil einer Anwendung, die aus einer mit ODBC verbundenen Datenbank liest und in diese schreibt. Es erstellt einen Datensatz in der Datenbank und prüft dann, ob ein Datensatz erfolgreich erstellt wurde, und kehrt dann zurück true.

Mein Verständnis des Kontrollflusses ist wie folgt:

command.ExecuteNonQuery()Es wird dokumentiert, dass ein Invalid​Operation​Exception"ein Methodenaufruf für den aktuellen Status des Objekts ungültig ist" ausgelöst wird. Wenn dies passieren würde, würde die Ausführung des tryBlocks anhalten, der finallyBlock würde ausgeführt und dann return false;die unten ausgeführt.

Meine IDE behauptet jedoch, dass der return false;Code nicht erreichbar ist. Und es scheint wahr zu sein, ich kann es entfernen und es kompiliert ohne Beschwerden. Für mich sieht es jedoch so aus, als gäbe es keinen Rückgabewert für den Codepfad, in dem die erwähnte Ausnahme ausgelöst wird.

private static bool createRecord(String table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1;
    } finally {
        command.Dispose();
    }

    return false;
}

Was ist mein Verständnisfehler hier?



41
Randnotiz: nicht Disposeexplizit anrufen , sondern setzen using:using (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
Dmitry Bychenko

7
Ein finallyBlock bedeutet etwas anderes als Sie denken.
Thorbjørn Ravn Andersen

Antworten:


149

Compiler-Warnung (Stufe 2) CS0162

Nicht erreichbarer Code erkannt

Der Compiler hat Code erkannt, der niemals ausgeführt wird.

Das heißt nur, dass der Compiler durch statische Analyse genug versteht, dass er nicht erreicht werden kann, und ihn vollständig aus der kompilierten IL auslässt (daher Ihre Warnung).

Hinweis : Sie können diese Tatsache selbst beweisen, indem Sie versuchen, mit dem Debugger auf den nicht erreichbaren Code zuzugreifen, oder einen IL Explorer verwenden

Das finallykann auf einer Ausnahme ausgeführt werden (obwohl dies beiseite gelegt wird), ändert nichts an der Tatsache (in diesem Fall), dass es sich immer noch um eine nicht erfasste Ausnahme handelt . Ergo wird der letzte returntrotzdem nie getroffen.

  • Wenn Sie den Code weiterhin auf dem letzten wollen return, ist Ihre einzige Option fangen die Ausnahme ;

  • Wenn Sie dies nicht tun, lassen Sie es einfach so wie es ist und entfernen Sie das return.

Beispiel

try 
{
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    return returnValue == 1;
}
catch(<some exception>)
{
   // do something
}
finally 
{
    command.Dispose();
}

return false;

Um die Dokumentation zu zitieren

try-finally (C # -Referenz)

Mit einem finally-Block können Sie alle Ressourcen bereinigen, die in einem try-Block zugewiesen sind, und Sie können Code ausführen, auch wenn im try-Block eine Ausnahme auftritt. In der Regel werden die Anweisungen eines finally-Blocks ausgeführt, wenn das Steuerelement eine try-Anweisung hinterlässt. Die Übertragung der Kontrolle kann als Ergebnis einer normalen Ausführung, der Ausführung einer break-, continue-, goto- oder return-Anweisung oder der Weitergabe einer Ausnahme aus der try-Anweisung erfolgen.

Innerhalb einer behandelten Ausnahme wird garantiert, dass der zugehörige finally-Block ausgeführt wird. Wenn die Ausnahme jedoch nicht behandelt wird, hängt die Ausführung des finally-Blocks davon ab, wie der Vorgang zum Abwickeln der Ausnahme ausgelöst wird. Dies hängt wiederum davon ab, wie Ihr Computer eingerichtet ist.

Wenn eine nicht behandelte Ausnahme eine Anwendung beendet, ist es normalerweise nicht wichtig, ob der finally-Block ausgeführt wird oder nicht. Wenn Sie jedoch Anweisungen in einem finally-Block haben, die auch in dieser Situation ausgeführt werden müssen, besteht eine Lösung darin, der try-finally-Anweisung einen catch-Block hinzuzufügen . Alternativ können Sie die Ausnahme abfangen, die möglicherweise im try-Block einer try-finally-Anweisung weiter oben im Aufrufstapel ausgelöst wird . Das heißt, Sie können die Ausnahme in der Methode abfangen, die die Methode aufruft, die die try-finally-Anweisung enthält, oder in der Methode, die diese Methode aufruft, oder in einer beliebigen Methode im Aufrufstapel. Wenn die Ausnahme nicht abgefangen wird, hängt die Ausführung des finally-Blocks davon ab, ob das Betriebssystem eine Ausnahme-Abwicklungsoperation auslöst.

zuletzt

Wenn Sie etwas verwenden, das die IDisposableSchnittstelle unterstützt (die nicht verwaltete Ressourcen freigibt), können Sie sie in eine usingAnweisung einschließen. Der Compiler generiert einen try {} finally {}und ruft Dispose()das Objekt intern auf


1
Was meinst du mit IL in den ersten Sätzen?
Uhrwerk

2
@Clockwork IL ist ein Produkt der Kompilierung von Code, der in .NET-Hochsprachen geschrieben ist. Sobald Sie Ihren in einer dieser Sprachen geschriebenen Code kompiliert haben, erhalten Sie eine Binärdatei, die aus IL besteht. Beachten Sie, dass Intermediate Language manchmal auch als Common Intermediate Language (CIL) oder Microsoft Intermediate Language (MSIL) bezeichnet wird.
TheGeneral

1
Kurz gesagt, weil er die folgenden Möglichkeiten nicht erkannt hat: Entweder wird der Versuch ausgeführt, bis er auf return trifft, und somit wird die unten stehende Rückgabe endgültig ignoriert, ODER es wird eine Ausnahme ausgelöst, und diese Rückgabe wird nie erreicht, da die Funktion aufgrund einer Ausnahme beendet wird geworfen.
Felype

86

Der finally-Block würde ausgeführt und dann die Rückgabe false ausgeführt. unten.

Falsch. finallyschluckt die Ausnahme nicht. Es ehrt es und die Ausnahme wird wie gewohnt ausgelöst. Der Code wird erst in der Datei final ausgeführt, bevor der Block endet (mit oder ohne Ausnahme).

Wenn Sie möchten, dass die Ausnahme verschluckt wird, sollten Sie einen catchBlock ohne Nein verwenden throw.


1
Wird das obige sinppet in Ausnahmefällen kompiliert, was wird zurückgegeben?
Ehsan Sajjad

3
Es wird zwar kompiliert, aber es wird niemals getroffen, return falseda es stattdessen eine Ausnahme auslöst. @EhsanSajjad
Patrick Hofman

1
scheint seltsam, kompiliert, weil entweder ein Wert für bool zurückgegeben wird, wenn keine Ausnahme vorliegt, und im Falle einer Ausnahme nichts, also legitim, um den Rückgabetyp der Methode zu erfüllen?
Ehsan Sajjad

2
Der Compiler ignoriert die Zeile einfach, dafür ist die Warnung gedacht. Warum ist das so seltsam? @EhsanSajjad
Patrick Hofman

3
Unterhaltsame Tatsache: Es ist eigentlich nicht garantiert, dass ein finally-Block ausgeführt wird, wenn die Ausnahme nicht im Programm abgefangen wird. Die Spezifikation garantiert dies nicht und frühe CLRs haben den finally-Block NICHT ausgeführt. Ich denke, ab 4.0 (möglicherweise früher) hat sich das Verhalten geändert, aber andere Laufzeiten verhalten sich möglicherweise immer noch anders. Für ziemlich überraschendes Verhalten.
Voo

27

Die Warnung ist, weil Sie nicht verwendet haben catchund Ihre Methode im Grunde so geschrieben ist:

bool SomeMethod()
{
    return true;
    return false; // CS0162 Unreachable code detected
}

Da Sie finallynur zur Entsorgung verwenden, besteht die bevorzugte Lösung darin, usingMuster zu verwenden:

using(var command = new WhateverCommand())
{
     ...
}

Das reicht aus, um sicherzustellen, was genannt Disposewird. Es wird garantiert entweder nach erfolgreicher Ausführung des Codeblocks oder nach (vor) einem catch Ausfall im Aufrufstapel aufgerufen (übergeordnete Aufrufe sind ausgefallen, oder?).

Wenn es nicht darum geht, zu entsorgen, dann

try { ...; return true; } // only one return
finally { ... }

ist ausreichend, da Sie am Ende der Methode nie zurückkehren müssen false(diese Zeile ist nicht erforderlich). Ihre Methode gibt entweder das Ergebnis der Befehlsausführung zurück ( trueoder false) oder löst andernfalls eine Ausnahme aus .


Ziehen Sie auch in Betracht, eigene Ausnahmen auszulösen, indem Sie erwartete Ausnahmen einschließen ( siehe InvalidOperationException-Konstruktor ):

try { ... }
catch(SomeExpectedException e)
{
    throw new SomeBetterExceptionWithExplanaition("...", e);
}

Dies wird normalerweise verwendet, um dem Anrufer etwas Bedeutenderes (Nützlicheres) zu sagen, als eine verschachtelte Anrufausnahme aussagen würde.


Meistens interessieren Sie sich nicht wirklich für unbehandelte Ausnahmen. Manchmal müssen Sie sicherstellen, dass dies finallyaufgerufen wird, auch wenn die Ausnahme nicht behandelt wird. In diesem Fall fangen Sie es einfach selbst und werfen es erneut (siehe diese Antwort ):

try { ... }
catch { ...; throw; } // re-throw
finally { ... }

14

Anscheinend suchen Sie nach so etwas:

private static bool createRecord(string table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {
  [... some other code ...]

  // Using: do not call Dispose() explicitly, but wrap IDisposable into using
  using (var command = ...) {
    try {
      // Normal flow:
      command.CommandText = sb.ToString();

      // True if and only if exactly one record affected
      return command.ExecuteNonQuery() == 1;
    }
    catch (DbException) {
      // Exceptional flow (all database exceptions)
      return false;
    }
  }
}

Bitte beachten Sie, dass finally dies keine Ausnahme verschluckt

finally {
  // This code will be executed; the exception will be efficently re-thrown
}

// And this code will never be reached

8

Sie haben keinen catchBlock, daher wird immer noch die Ausnahme ausgelöst, die die Rückgabe blockiert.

Der finally-Block würde ausgeführt und dann die Rückgabe false ausgeführt. unten.

Dies ist falsch, da der finally-Block ausgeführt würde und es dann eine nicht erfasste Ausnahme geben würde.

finallyBlöcke werden zur Bereinigung verwendet und erfassen die Ausnahme nicht. Die Ausnahme wird vor der Rückgabe ausgelöst, daher wird die Rückgabe nie erreicht, da zuvor eine Ausnahme ausgelöst wurde.

Ihre IDE ist korrekt, dass sie niemals erreicht wird, da die Ausnahme ausgelöst wird. Nur catchBlöcke können Ausnahmen abfangen.

Lesen aus der Dokumentation ,

Wenn eine nicht behandelte Ausnahme eine Anwendung beendet, ist es normalerweise nicht wichtig, ob der finally-Block ausgeführt wird oder nicht. Wenn Sie jedoch Anweisungen in einem finally-Block haben, die auch in dieser Situation ausgeführt werden müssen, besteht eine Lösung darin , der try-finally-Anweisung einen catch-Block hinzuzufügen . Alternativ können Sie die Ausnahme abfangen, die möglicherweise im try-Block einer try-finally-Anweisung weiter oben im Aufrufstapel ausgelöst wird. Das heißt, Sie können die Ausnahme in der Methode abfangen, die die Methode aufruft, die die try-finally-Anweisung enthält, oder in der Methode, die diese Methode aufruft, oder in einer beliebigen Methode im Aufrufstapel. Wenn die Ausnahme nicht abgefangen wird, hängt die Ausführung des finally-Blocks davon ab, ob das Betriebssystem eine Ausnahme-Abwicklungsoperation auslöst .

Dies zeigt deutlich, dass das endgültige Ziel nicht darin besteht, die Ausnahme abzufangen, und Sie hätten Recht gehabt, wenn catchvor der finallyAnweisung eine leere Anweisung vorhanden gewesen wäre .


7

Wenn die Ausnahme ausgelöst wird, wird der Stapel abgewickelt (die Ausführung verlässt die Funktion), ohne dass ein Wert zurückgegeben wird, und jeder catch-Block in den Stapelrahmen über der Funktion fängt stattdessen die Ausnahme ab.

Daher return falsewird nie ausgeführt.

Versuchen Sie, manuell eine Ausnahme auszulösen, um den Kontrollfluss zu verstehen:

try {
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    // Try this.
    throw new Exception("See where this goes.");

    return returnValue == 1;
} finally {
    command.Dispose();
}

5

Auf Ihrem Code:

private static bool createRecord(String table, IDictionary<String,String> data, System.Data.IDbConnection conn, OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1; // You return here in case no exception is thrown
    } finally {
        command.Dispose(); //You don't have a catch so the exception is passed on if thrown
    }

    return false; // This is never executed because there was either one of the above two exit points of the method reached.
}

Der finally-Block würde ausgeführt und dann die Rückgabe false ausgeführt. unten

Dies ist der Fehler in Ihrer Logik, da der finallyBlock die Ausnahme nicht abfängt und niemals die letzte return-Anweisung erreicht.


4

Die letzte Anweisung return falseist nicht erreichbar, da dem try-Block ein catchTeil fehlt , der die Ausnahme behandeln würde. Daher wird die Ausnahme nach dem finallyBlock erneut ausgelöst und die Ausführung erreicht nie die letzte Anweisung.


2

Sie haben zwei Rückgabewege in Ihrem Code, von denen der zweite wegen des ersten nicht erreichbar ist. Die letzte Anweisung in Ihrem tryBlock return returnValue == 1;liefert Ihre normale Rendite, sodass Sie die nie erreichen könnenreturn false; am Ende des Methodenblocks .

FWIW, Reihenfolge der Prüfung in Bezug auf die finally Block lautet: Der Ausdruck, der den Rückgabewert im try-Block liefert, wird zuerst ausgewertet, dann wird der finally-Block ausgeführt und dann wird der berechnete Ausdruckswert zurückgegeben (innerhalb des try-Blocks).

In Bezug auf den Ablauf bei Ausnahme ... ohne a catchwird der finallybei Ausnahme ausgeführt, bevor die Ausnahme dann aus der Methode entfernt wird. Es gibt keinen "Rückweg".

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.