Codice non raggiungibile, ma raggiungibile con un'eccezione


108

Questo codice fa parte di un'applicazione che legge e scrive in un database connesso ODBC. Crea un record nel database e quindi controlla se un record è stato creato correttamente, quindi restituisce true.

La mia comprensione del flusso di controllo è la seguente:

command.ExecuteNonQuery()è documentato per lanciare un Invalid​Operation​Exceptionquando "una chiamata al metodo non è valida per lo stato corrente dell'oggetto". Pertanto, se ciò accadesse, l'esecuzione del tryblocco si fermerebbe, il finallyblocco verrebbe eseguito, quindi eseguirà il return false;in fondo.

Tuttavia, il mio IDE afferma che il return false;codice non è raggiungibile. E sembra essere vero, posso rimuoverlo e si compila senza lamentele. Tuttavia, per me sembra che non ci sarebbe alcun valore di ritorno per il percorso del codice in cui viene generata l'eccezione menzionata.

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

Qual è il mio errore di comprensione qui?



41
Nota a Disposeusingusing (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
margine

7
Un finallyblocco significa qualcos'altro di quanto pensi.
Thorbjørn Ravn Andersen

Risposte:


149

Avviso del compilatore (livello 2) CS0162

Rilevato codice non raggiungibile

Il compilatore ha rilevato codice che non verrà mai eseguito.

Il che sta solo dicendo, il compilatore capisce abbastanza attraverso l' analisi statica che non può essere raggiunto e lo omette completamente dall'IL compilato (da qui il tuo avviso)

Nota : puoi provare questo fatto a te stesso provando a passare al codice irraggiungibile con il debugger o utilizzando un IL Explorer

La finallypuò essere eseguito su un eccezione , (anche se questo a parte) non cambia il fatto (in questo caso) sarà ancora un'eccezione non rilevata . Ergo, l'ultimo returnnon verrà mai colpito a prescindere.

  • Se vuoi che il codice continui fino all'ultimo return, la tua unica opzione è Catch the Exception ;

  • Se non lo fai, lascialo così com'è e rimuovi il file return.

Esempio

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

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

return false;

Per citare la documentazione

try-finalmente (riferimenti per C #)

Utilizzando un blocco finalmente, è possibile pulire tutte le risorse allocate in un blocco try ed è possibile eseguire il codice anche se si verifica un'eccezione nel blocco try. In genere, le istruzioni di un blocco finalmente vengono eseguite quando il controllo lascia un'istruzione try. Il trasferimento del controllo può avvenire come risultato della normale esecuzione, dell'esecuzione di un'istruzione break, continue, goto o return, o della propagazione di un'eccezione dall'istruzione try.

All'interno di un'eccezione gestita, è garantito che venga eseguito il blocco finalmente associato. Tuttavia, se l'eccezione non viene gestita, l'esecuzione del blocco latest dipende da come viene attivata l'operazione di annullamento dell'eccezione. Ciò, a sua volta, dipende da come è configurato il tuo computer.

Di solito, quando un'eccezione non gestita termina un'applicazione, non è importante se il blocco finalmente viene eseguito o meno. Tuttavia, se in un blocco finalmente sono presenti istruzioni che devono essere eseguite anche in quella situazione, una soluzione è aggiungere un blocco catch all'istruzione try-latest . In alternativa, puoi catturare l'eccezione che potrebbe essere generata nel blocco try di un'istruzione try-latest più in alto nello stack di chiamate . Cioè, puoi catturare l'eccezione nel metodo che chiama il metodo che contiene l'istruzione try-latest, o nel metodo che chiama quel metodo o in qualsiasi metodo nello stack di chiamate. Se l'eccezione non viene rilevata, l'esecuzione del blocco latest dipende dal fatto che il sistema operativo scelga di attivare un'operazione di rimozione dell'eccezione.

infine

Quando si utilizza qualsiasi cosa che supporti l' IDisposableinterfaccia (progettata per rilasciare risorse non gestite), è possibile racchiuderla in usingun'istruzione. Il compilatore genererà una try {} finally {}chiamata interna Dispose()sull'oggetto


1
Cosa intendi con IL nelle prime frasi?
Clockwork

2
@Clockwork IL è un prodotto di compilazione di codice scritto in linguaggi .NET di alto livello. Dopo aver compilato il codice scritto in uno di questi linguaggi, otterrai un file binario fatto di IL. Si noti che Intermediate Language è talvolta chiamato anche Common Intermediate Language (CIL) o Microsoft Intermediate Language (MSIL).,
Michael Randall

1
In breve, poiché non ha colto le possibilità sono: o il tentativo viene eseguito fino a quando non colpisce return e quindi ignora il ritorno sotto alla fine OPPURE viene generata un'eccezione e quel ritorno non viene mai raggiunto perché la funzione uscirà a causa di un'eccezione gettato.
Felype

86

il blocco finalmente verrà eseguito, quindi verrà eseguito il ritorno false; in fondo.

Sbagliato. finallynon ingoia l'eccezione. Lo onora e l'eccezione verrà lanciata normalmente. Eseguirà solo il codice nella parte finale prima che il blocco termini (con o senza eccezioni).

Se vuoi che l'eccezione venga inghiottita, dovresti usare un catchblocco senza throwdentro.


1
il sinppet di cui sopra verrà compilato in caso di eccezione, cosa verrà restituito?
Ehsan Sajjad

3
Si compila, ma non verrà mai visualizzato return falsepoiché genererà un'eccezione @EhsanSajjad
Patrick Hofman

1
sembra strano, si compila perché o restituirà un valore per bool in caso di nessuna eccezione e in caso di eccezione non sarà nulla, quindi legittimo per soddisfare il tipo di ritorno del metodo?
Ehsan Sajjad

2
Il compilatore ignorerà semplicemente la riga, a questo serve l'avvertimento. Allora perché è strano? @EhsanSajjad
Patrick Hofman

3
Fatto divertente: in realtà non è garantito che un blocco finalmente venga eseguito se l'eccezione non viene rilevata nel programma. Le specifiche non garantiscono questo e i primi CLR NON eseguivano il blocco finalmente. Penso che a partire dalla 4.0 (potrebbe essere stata prima) il comportamento è cambiato, ma altri tempi di esecuzione potrebbero ancora comportarsi in modo diverso. Rende un comportamento piuttosto sorprendente.
Voo

27

L'avvertimento è perché non l'hai usato catche il tuo metodo è fondamentalmente scritto in questo modo:

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

Poiché si utilizza finallyesclusivamente per smaltire, la soluzione preferita è utilizzare il usingmodello:

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

È sufficiente, per garantire ciò Disposeche verrà chiamato. È garantito che venga chiamato dopo l'esecuzione riuscita del blocco di codice o su (prima) alcuni in catch basso nello stack di chiamate (le chiamate principali sono inattive, giusto?).

Se non si tratta di smaltire, allora

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

è sufficiente, dal momento che non dovrai mai tornare falsealla fine del metodo (non c'è bisogno di quella riga). Il tuo metodo è il risultato di ritorno dell'esecuzione del comando ( trueo false) o altrimenti genererà un'eccezione .


Considera anche di lanciare le tue eccezioni avvolgendo le eccezioni previste (controlla il costruttore InvalidOperationException ):

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

Questo è tipicamente usato per dire qualcosa di più significativo (utile) al chiamante di quanto direbbe un'eccezione di chiamata annidata.


La maggior parte delle volte non ti interessano davvero le eccezioni non gestite. A volte è necessario assicurarsi che finallyvenga chiamato anche se l'eccezione non è gestita. In questo caso lo prendi semplicemente da solo e rilancia (vedi questa risposta ):

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

14

Sembra che tu stia cercando qualcosa del genere:

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

Per favore, nota, questo finally non inghiotte alcuna eccezione

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

// And this code will never be reached

8

Non hai un catchblocco, quindi l'eccezione viene comunque lanciata, il che blocca il ritorno.

il blocco finalmente verrà eseguito, quindi verrà eseguito il ritorno false; in fondo.

Questo è sbagliato, perché il blocco finalmente verrebbe eseguito e quindi ci sarebbe un'eccezione non rilevata.

finallyi blocchi vengono utilizzati per la pulizia e non catturano l'eccezione. L'eccezione viene lanciata prima del ritorno, quindi, il ritorno non sarà mai raggiunto, perché prima viene lanciata un'eccezione.

Il tuo IDE è corretto che non sarà mai raggiunto, perché verrà generata l'eccezione. Solo i catchblocchi sono in grado di catturare le eccezioni.

Leggendo dalla documentazione ,

Di solito, quando un'eccezione non gestita termina un'applicazione, non è importante se il blocco finalmente viene eseguito o meno. Tuttavia, se in un blocco finalmente sono presenti istruzioni che devono essere eseguite anche in quella situazione, una soluzione è aggiungere un blocco catch all'istruzione try-latest . In alternativa, puoi catturare l'eccezione che potrebbe essere generata nel blocco try di un'istruzione try-latest più in alto nello stack di chiamate. Cioè, puoi catturare l'eccezione nel metodo che chiama il metodo che contiene l'istruzione try-latest, o nel metodo che chiama quel metodo o in qualsiasi metodo nello stack di chiamate. Se l'eccezione non viene rilevata, l'esecuzione del blocco latest dipende dal fatto che il sistema operativo scelga di attivare un'operazione di annullamento dell'eccezione .

Questo mostra chiaramente che finalmente non è inteso per catturare l'eccezione, e avresti avuto ragione se ci fosse stata un'istruzione vuota catchprima finallydell'istruzione.


7

Quando viene lanciata l'eccezione, lo stack si srotolerà (l'esecuzione uscirà dalla funzione) senza restituire un valore e qualsiasi blocco catch negli stack frame sopra la funzione catturerà invece l'eccezione.

Quindi, return falsenon verrà mai eseguito.

Prova a lanciare manualmente un'eccezione per comprendere il flusso di controllo:

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

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

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

5

Sul tuo codice:

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

il blocco finalmente verrà eseguito, quindi verrà eseguito il ritorno false; in fondo

Questo è il difetto nella tua logica perché il finallyblocco non cattura l'eccezione e non raggiungerà mai l'ultima istruzione return.


4

L'ultima istruzione return falseè irraggiungibile, perché al blocco try manca una catchparte che gestirà l'eccezione, quindi l'eccezione viene rilanciata dopo il finallyblocco e l'esecuzione non raggiunge mai l'ultima istruzione.


2

Hai due percorsi di ritorno nel codice, il secondo dei quali non è raggiungibile a causa del primo. L'ultima istruzione nel tuo tryblocco return returnValue == 1;fornisce il tuo ritorno normale, quindi non puoi mai raggiungere ilreturn false; la fine del blocco del metodo.

FWIW, l'ordine di esecuzione relativo al finallyblocco è: prima verrà valutata l'espressione che fornisce il valore di ritorno nel blocco try, quindi verrà eseguito il blocco finalmente e quindi verrà restituito il valore dell'espressione calcolato (all'interno del blocco try).

Per quanto riguarda il flusso su eccezione ... senza un catch, finallyverrà eseguito su eccezione prima che l'eccezione venga quindi rilanciata dal metodo; non esiste un percorso di "ritorno".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.