Nieosiągalny kod, ale osiągalny z wyjątkiem

108

Ten kod jest częścią aplikacji, która odczytuje i zapisuje w bazie danych połączonej z ODBC. Tworzy rekord w bazie danych, a następnie sprawdza, czy rekord został pomyślnie utworzony, a następnie zwraca true.

Moje rozumienie przepływu kontroli jest następujące:

command.ExecuteNonQuery()jest udokumentowane, aby Invalid​Operation​Exceptionzgłosić, gdy „wywołanie metody jest nieprawidłowe dla bieżącego stanu obiektu”. Dlatego, gdyby tak się stało, wykonanie trybloku zatrzymałoby się, finallyblok zostałby wykonany, a następnie zostałby wykonany return false;na dole.

Jednak moje IDE twierdzi, że return false;kod jest nieosiągalny. I wydaje się, że to prawda, mogę go usunąć i kompiluje bez żadnych skarg. Jednak dla mnie wygląda na to, że nie byłoby wartości zwracanej dla ścieżki kodu, w której jest zgłaszany wspomniany wyjątek.

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;
}

Jaki jest mój błąd zrozumienia?

0xCAFEBABE
źródło
41
Uwaga dodatkowa: nie dzwoń Disposewyraźnie, ale umieść using:using (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
Dmitry Bychenko
7
finallyBlok oznacza coś innego niż myślisz.
Thorbjørn Ravn Andersen

Odpowiedzi:

149

Ostrzeżenie kompilatora (poziom 2) CS0162

Wykryto nieosiągalny kod

Kompilator wykrył kod, który nigdy nie zostanie wykonany.

To tylko mówi, że Kompilator rozumie wystarczająco poprzez analizę statyczną , że nie można go osiągnąć i całkowicie pomija go w skompilowanym IL (stąd twoje ostrzeżenie)

Uwaga : możesz udowodnić sobie ten fakt, próbując przejść do nieosiągalnego kodu za pomocą debugera lub używając narzędzia IL Explorer

finallyMoże uruchomić na wyjątek , (mimo że na bok) nie zmienia faktu (w tym przypadku), to nadal być przechwycony wyjątek . Ergo, ten ostatni returnnigdy nie zostanie trafiony.

  • Jeśli chcesz kod do kontynuowania na ostatniej return, jedyną opcją jest, aby złapać ten wyjątek ;

  • Jeśli tego nie zrobisz, po prostu zostaw to tak, jak jest i usuń return.

Przykład

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

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

return false;

Cytując dokumentację

try-last (odwołanie w C #)

Używając bloku last, możesz wyczyścić wszystkie zasoby przydzielone w bloku try i uruchomić kod, nawet jeśli w bloku try wystąpi wyjątek. Zazwyczaj instrukcje ostatniego bloku uruchamianego, gdy sterowanie opuszcza instrukcję try. Przekazanie kontroli może nastąpić w wyniku normalnego wykonania, wykonania instrukcji break, continue, goto lub return lub propagacji wyjątku z instrukcji try.

W obsługiwanym wyjątku gwarantowane jest uruchomienie skojarzonego bloku końcowego. Jeśli jednak wyjątek nie jest obsługiwany, wykonanie ostatniego bloku jest zależne od sposobu wyzwalania operacji wycofywania wyjątku. To z kolei zależy od konfiguracji komputera.

Zwykle, gdy nieobsługiwany wyjątek kończy aplikację, to, czy ostatni blok zostanie uruchomiony, nie jest ważne. Jeśli jednak masz instrukcje w bloku final, które muszą zostać uruchomione nawet w takiej sytuacji, jednym z rozwiązań jest dodanie bloku catch do instrukcji try-last . Alternatywnie można przechwycić wyjątek, który może zostać wyrzucony w bloku try instrukcji try-last na stosie wywołań . Oznacza to, że można złapać wyjątek w metodzie, która wywołuje metodę, która zawiera instrukcję try-last, lub w metodzie, która wywołuje tę metodę, lub w dowolnej metodzie na stosie wywołań. Jeśli wyjątek nie zostanie przechwycony, wykonanie ostatniego bloku zależy od tego, czy system operacyjny zdecyduje się wyzwolić operację wycofania wyjątku.

W końcu

Korzystając z czegokolwiek, co obsługuje IDisposableinterfejs (który jest przeznaczony do zwalniania niezarządzanych zasobów), można opakować to w usinginstrukcję. Kompilator wygeneruje try {} finally {}wewnętrzne wywołanie Dispose()obiektu

Michael Randall
źródło
1
Co masz na myśli mówiąc IL w pierwszych zdaniach?
Clockwork
2
@Clockwork IL jest produktem kompilacji kodu napisanego w językach .NET wysokiego poziomu. Po skompilowaniu kodu napisanego w jednym z tych języków otrzymasz plik binarny utworzony z języka IL. Należy pamiętać, że język pośredni jest czasami nazywany również wspólnym językiem pośrednim (CIL) lub językiem pośrednim firmy Microsoft (MSIL).,
Michael Randall
1
Krótko mówiąc, ponieważ nie wychwycił możliwości to: Albo try działa, dopóki nie trafi return, a tym samym ostatecznie zignoruje powrót poniżej LUB wyjątek jest zgłaszany i ten powrót nigdy nie jest osiągany, ponieważ funkcja zakończy działanie z powodu wystąpienia wyjątku rzucony.
Felype
86

ostatni blok zostałby wykonany, a następnie zwróciłby false; na dnie.

Źle. finallynie połyka wyjątku. Szanuje to i wyjątek zostanie zgłoszony normalnie. Wykona kod tylko w końcu przed końcem bloku (z wyjątkiem lub bez).

Jeśli chcesz, aby wyjątek został połknięty, powinieneś użyć catchbloku bez throww nim.

Patrick Hofman
źródło
1
czy powyższy sinppet zostanie skompilowany w przypadku wyjątku, co zostanie zwrócone?
Ehsan Sajjad
3
return falseKompiluje , ale nigdy nie trafi, ponieważ zamiast tego wyrzuci wyjątek @EhsanSajjad
Patrick Hofman
1
wydaje się dziwne, kompiluje się, ponieważ albo zwróci wartość bool w przypadku braku wyjątku, aw przypadku wyjątku nic nie będzie, więc prawidłowe dla spełnienia typu zwrotu metody?
Ehsan Sajjad
2
Kompilator po prostu zignoruje linię, do tego służy ostrzeżenie. Więc dlaczego to jest dziwne? @EhsanSajjad
Patrick Hofman
3
Ciekawostka: W rzeczywistości nie ma gwarancji, że ostatecznie blok zostanie uruchomiony, jeśli wyjątek nie zostanie przechwycony w programie. Specyfikacja tego nie gwarantuje, a wczesne środowiska CLR NIE wykonywały ostatniego bloku. Myślę, że począwszy od 4.0 (mogło być wcześniej) to zachowanie się zmieniło, ale inne środowiska wykonawcze mogą nadal zachowywać się inaczej. Sprawia raczej zaskakujące zachowanie.
Voo
27

Ostrzeżenie jest takie, ponieważ nie użyłeś, catcha twoja metoda jest zasadniczo napisana w ten sposób:

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

Ponieważ używasz finallywyłącznie do utylizacji, preferowanym rozwiązaniem jest użycie usingwzoru:

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

To wystarczy, aby zapewnić, jak Disposezostanie nazwane. Jest gwarantowane, że zostanie wywołane albo po pomyślnym wykonaniu bloku kodu, albo po (przed) jego catch wyłączeniu w stosie połączeń (połączenia macierzyste są w dół, prawda?).

Jeśli nie chodzi o pozbycie się, to

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

wystarczy, ponieważ nigdy nie będziesz musiał wracać falsena koniec metody (nie ma potrzeby tego wiersza). Twoja metoda zwraca wynik wykonania polecenia ( truelub false) lub w przeciwnym razie zgłosi wyjątek .


Rozważ również zgłoszenie własnych wyjątków przez zawijanie oczekiwanych wyjątków (sprawdź konstruktor InvalidOperationException ):

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

Zwykle jest to używane do powiedzenia wywołującemu coś bardziej znaczącego (użytecznego) niż mógłby powiedzieć wyjątek zagnieżdżonego połączenia.


W większości przypadków nie obchodzą Cię nieobsługiwane wyjątki. Czasami trzeba upewnić się, że finallyjest wywoływana, nawet jeśli wyjątek nie jest obsługiwany. W takim przypadku po prostu złap go sam i rzuć ponownie (zobacz odpowiedź ):

try { ... }
catch { ...; throw; } // re-throw
finally { ... }
Sinatr
źródło
14

Wygląda na to, że szukasz czegoś takiego:

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;
    }
  }
}

Proszę zauważyć, że finally to nie wyklucza żadnego wyjątku

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

// And this code will never be reached
Dmitrij Bychenko
źródło
8

Nie masz catchbloku, więc nadal jest zgłaszany wyjątek, który blokuje powrót.

ostatni blok zostałby wykonany, a następnie zwróciłby false; na dnie.

To jest złe, ponieważ ostatni blok zostałby wykonany, a następnie wystąpiłby nieprzechwycony wyjątek.

finallybloki są używane do czyszczenia i nie przechwytują wyjątku. Wyjątek jest zgłaszany przed powrotem, dlatego zwrot nigdy nie zostanie osiągnięty, ponieważ wcześniej został zgłoszony wyjątek.

Twoje IDE jest poprawne, ponieważ nigdy nie zostanie osiągnięte, ponieważ wyjątek zostanie zgłoszony. Tylkocatch bloki mogą wychwytywać wyjątki.

Czytanie z dokumentacji ,

Zwykle, gdy nieobsługiwany wyjątek kończy aplikację, to, czy ostatni blok zostanie uruchomiony, nie jest ważne. Jeśli jednak masz instrukcje w bloku final, które muszą zostać uruchomione nawet w takiej sytuacji, jednym z rozwiązań jest dodanie bloku catch do instrukcji try-last . Alternatywnie można przechwycić wyjątek, który może zostać wyrzucony w bloku try instrukcji try-last na stosie wywołań. Oznacza to, że można złapać wyjątek w metodzie, która wywołuje metodę, która zawiera instrukcję try-last, lub w metodzie, która wywołuje tę metodę, lub w dowolnej metodzie na stosie wywołań. Jeśli wyjątek nie zostanie przechwycony, wykonanie ostatniego bloku zależy od tego, czy system operacyjny zdecyduje się wyzwolić operację wycofania wyjątku .

To wyraźnie pokazuje, że final nie jest przeznaczony do wychwytywania wyjątku i byłbyś poprawny, gdyby catchprzed instrukcją znajdowała się pusta finallyinstrukcja.

Ray Wu
źródło
7

Gdy wyjątek zostanie zgłoszony, stos się rozwinie (wykonanie wyjdzie z funkcji) bez zwracania wartości, a zamiast tego każdy blok catch w ramkach stosu powyżej funkcji przechwyci wyjątek.

Dlatego return falsenigdy nie będzie wykonywany.

Spróbuj ręcznie zgłosić wyjątek, aby zrozumieć przepływ sterowania:

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

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

    return returnValue == 1;
} finally {
    command.Dispose();
}
Nisarg
źródło
5

W Twoim kodzie:

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.
}

ostatni blok zostałby wykonany, a następnie zwróciłby false; na dnie

To jest wada w twojej logice, ponieważ finallyblok nie przechwyci wyjątku i nigdy nie osiągnie ostatniej instrukcji return.

meJustAndrew
źródło
4

Ostatnia instrukcja return falsejest nieosiągalna, ponieważ w bloku try brakuje catchczęści, która obsługiwałaby wyjątek, więc wyjątek jest ponownie zgłaszany po finallybloku, a wykonanie nigdy nie dochodzi do ostatniej instrukcji.

Martin Staufcik
źródło
2

Masz w kodzie dwie ścieżki powrotu, z których druga jest nieosiągalna z powodu pierwszej. Ostatnia instrukcja w twoim trybloku return returnValue == 1;zapewnia normalny zwrot, więc nigdy nie możesz dotrzeć return false;do końca bloku metody.

FWIW, kolejność sprawdzania powiązana z finallyblokiem jest następująca: najpierw zostanie obliczone wyrażenie dostarczające zwracaną wartość w bloku try, następnie zostanie wykonany ostatni blok, a następnie zostanie zwrócona obliczona wartość wyrażenia (wewnątrz bloku try).

Odnośnie przepływu po wyjątku ... bez a catch, finallyzostanie wykonany po wyjątku, zanim wyjątek zostanie ponownie wyrzucony z metody; nie ma ścieżki „powrotu”.

C Robinson
źródło