Code inaccessible, mais accessible avec une exception

108

Ce code fait partie d'une application qui lit et écrit dans une base de données connectée ODBC. Il crée un enregistrement dans la base de données, puis vérifie si un enregistrement a été créé avec succès, puis revient true.

Ma compréhension du flux de contrôle est la suivante:

command.ExecuteNonQuery()est documenté pour lancer un Invalid​Operation​Exceptionquand "un appel de méthode n'est pas valide pour l'état actuel de l'objet". Par conséquent, si cela se produisait, l'exécution du trybloc s'arrêterait, le finallybloc serait exécuté, puis exécuterait le return false;en bas.

Cependant, mon IDE prétend que le return false;code est inaccessible. Et cela semble être vrai, je peux le supprimer et il se compile sans aucune plainte. Cependant, pour moi, il semble qu'il n'y aurait pas de valeur de retour pour le chemin du code où l'exception mentionnée est levée.

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

Quelle est mon erreur de compréhension ici?

0xCAFEBABE
la source
41
Note latérale: n'appelez pas Disposeexplicitement, mais mettez using:using (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
Dmitry Bychenko
7
Un finallybloc signifie autre chose que vous ne le pensez.
Thorbjørn Ravn Andersen

Réponses:

149

Avertissement du compilateur (niveau 2) CS0162

Code inaccessible détecté

Le compilateur a détecté du code qui ne sera jamais exécuté.

Ce qui veut juste dire, le compilateur comprend suffisamment grâce à l' analyse statique qu'il ne peut pas être atteint et l'omet complètement de l' IL compilé (d'où votre avertissement)

Remarque : vous pouvez prouver ce fait à vous-même en essayant d'accéder au code inaccessible avec le débogueur ou en utilisant un IL Explorer

Le finallypeut fonctionner sur une exception , (bien que cela mis à part) cela ne change pas le fait (dans ce cas), il sera toujours une exception non interceptée . Ergo, le dernier returnne sera jamais touché malgré tout.

  • Si vous voulez que le code continue sur le dernier return, votre seule option est d' attraper l' exception ;

  • Si vous ne le faites pas, laissez-le tel quel et supprimez le fichier return.

Exemple

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

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

return false;

Pour citer la documentation

try-finally (référence C #)

En utilisant un bloc finally, vous pouvez nettoyer toutes les ressources allouées dans un bloc try et vous pouvez exécuter du code même si une exception se produit dans le bloc try. En règle générale, les instructions d'un bloc finally s'exécutent lorsque le contrôle quitte une instruction try. Le transfert de contrôle peut se produire à la suite d'une exécution normale, de l'exécution d'une instruction break, continue, goto ou return, ou de la propagation d'une exception hors de l'instruction try.

Dans une exception gérée, l'exécution du bloc finally associé est garantie. Cependant, si l'exception n'est pas gérée, l'exécution du bloc finally dépend de la manière dont l'opération de déroulement de l'exception est déclenchée. Cela dépend à son tour de la façon dont votre ordinateur est configuré.

Généralement, lorsqu'une exception non gérée met fin à une application, le fait que le bloc finally soit exécuté ou non n'est pas important. Cependant, si vous avez des instructions dans un bloc finally qui doivent être exécutées même dans cette situation, une solution consiste à ajouter un bloc catch à l'instruction try-finally . Vous pouvez également intercepter l'exception qui pourrait être levée dans le bloc try d'une instruction try-finally plus haut dans la pile d'appels . Autrement dit, vous pouvez intercepter l'exception dans la méthode qui appelle la méthode qui contient l'instruction try-finally, ou dans la méthode qui appelle cette méthode, ou dans n'importe quelle méthode de la pile d'appels. Si l'exception n'est pas interceptée, l'exécution du bloc finally dépend du choix du système d'exploitation de déclencher une opération de déroulement d'exception.

enfin

Lorsque vous utilisez tout ce qui prend en charge l' IDisposableinterface (qui est conçue pour libérer des ressources non gérées), vous pouvez l'envelopper dans une usinginstruction. Le compilateur générera un try {} finally {}appel et en interne Dispose()sur l'objet

Michael Randall
la source
1
Qu'entendez-vous par IL dans les premières phrases?
Clockwork
2
@Clockwork IL est un produit de compilation de code écrit dans des langages .NET de haut niveau. Une fois que vous compilez votre code écrit dans l'un de ces langages, vous obtiendrez un binaire composé d'IL. Notez que le langage intermédiaire est parfois aussi appelé langage intermédiaire commun (CIL) ou Microsoft Intermediate Language (MSIL).,
Michael Randall
1
En bref, parce qu'il n'a pas saisi les possibilités sont: Soit l'essai s'exécute jusqu'à ce qu'il atteigne return et ignore donc le retour ci-dessous finalement OU une exception est levée et ce retour n'est jamais atteint car la fonction se terminera en raison d'une exception étant jeté.
Felype
86

le bloc final serait exécuté, puis exécuterait le retour faux; au fond.

Faux. finallyn'accepte pas l'exception. Il l'honore et l'exception sera levée comme d'habitude. Il n'exécutera le code que dans le finally avant la fin du bloc (avec ou sans exception).

Si vous voulez que l'exception soit avalée, vous devez utiliser un catchbloc sans aucun throw.

Patrick Hofman
la source
1
le sinppet ci-dessus compilera-t-il en cas d'exception, qu'est-ce qui sera retourné?
Ehsan Sajjad
3
Il compile, mais il ne frappera jamais return falsecar il lancera une exception à la place @EhsanSajjad
Patrick Hofman
1
semble étrange, compile parce que soit il retournera une valeur pour bool en cas de non exception et en cas d'exception rien ne sera, si légitime pour satisfaire le type de retour de la méthode?
Ehsan Sajjad
2
Le compilateur ignorera simplement la ligne, c'est à cela que sert l'avertissement. Alors pourquoi est-ce étrange? @EhsanSajjad
Patrick Hofman
3
Fait amusant: il n'est en fait pas garanti qu'un bloc finally s'exécutera si l'exception n'est pas interceptée dans le programme. La spécification ne garantit pas cela et les premiers CLR n'ont PAS exécuté le bloc finally. Je pense qu'à partir de la version 4.0 (peut-être plus tôt), ce comportement a changé, mais d'autres environnements d'exécution peuvent toujours se comporter différemment. Donne un comportement assez surprenant.
Voo
27

L'avertissement est que vous ne l'avez pas utilisé catchet que votre méthode est essentiellement écrite comme ceci:

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

Puisque vous utilisez finallyuniquement pour vous débarrasser, la solution préférée est d'utiliser le usingmodèle:

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

Cela suffit, pour assurer ce Disposequ'on appellera. Il est garanti qu'il sera appelé soit après l'exécution réussie du bloc de code, soit après (avant) une catch partie de la pile d'appels (les appels des parents sont arrêtés, non?).

S'il ne s'agissait pas d'éliminer, alors

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

suffit, puisque vous n'aurez jamais à revenir falseà la fin de la méthode (il n'y a pas besoin de cette ligne). Votre méthode renvoie le résultat de l'exécution de la commande ( trueou false) ou lèvera une exception dans le cas contraire .


Envisagez également de lever vos propres exceptions en encapsulant les exceptions attendues (consultez le constructeur InvalidOperationException ):

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

Ceci est généralement utilisé pour dire quelque chose de plus significatif (utile) à l'appelant que ne le dirait une exception d'appel imbriquée.


La plupart du temps, vous ne vous souciez pas vraiment des exceptions non gérées. Parfois, vous devez vous assurer qu'il finallyest appelé même si l'exception n'est pas gérée. Dans ce cas, vous l'attrapez vous-même et le relancez (voir cette réponse ):

try { ... }
catch { ...; throw; } // re-throw
finally { ... }
Sinatr
la source
14

Il semble que vous recherchez quelque chose comme ceci:

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

Veuillez noter que finally cela ne fait aucune exception

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

// And this code will never be reached
Dmitry Bychenko
la source
8

Vous n'avez pas de catchbloc, donc l'exception est toujours levée, ce qui bloque le retour.

le bloc final serait exécuté, puis exécuterait le retour faux; au fond.

C'est faux, car le bloc finally serait exécuté, puis il y aurait une exception non interceptée.

finallyles blocs sont utilisés pour le nettoyage et ils n'attrapent pas l'exception. L'exception est levée avant le retour, par conséquent, le retour ne sera jamais atteint, car une exception est levée avant.

Votre IDE est correct qu'il ne sera jamais atteint, car l'exception sera levée. Seuls les catchblocs peuvent attraper des exceptions.

Lecture de la documentation ,

Habituellement, lorsqu'une exception non gérée met fin à une application, le fait que le bloc finally soit exécuté ou non n'est pas important. Cependant, si vous avez des instructions dans un bloc finally qui doivent être exécutées même dans cette situation, une solution consiste à ajouter un bloc catch à l'instruction try-finally . Vous pouvez également intercepter l'exception qui pourrait être levée dans le bloc try d'une instruction try-finally plus haut dans la pile d'appels. Autrement dit, vous pouvez intercepter l'exception dans la méthode qui appelle la méthode qui contient l'instruction try-finally, ou dans la méthode qui appelle cette méthode, ou dans n'importe quelle méthode de la pile d'appels. Si l'exception n'est pas interceptée, l'exécution du bloc finally dépend du choix du système d'exploitation de déclencher une opération de déroulement d'exception .

Cela montre clairement que le finally n'est pas destiné à intercepter l'exception, et vous auriez eu raison s'il y avait eu une catchinstruction vide avant l' finallyinstruction.

Ray Wu
la source
7

Lorsque l'exception est levée, la pile se déroule (l'exécution sort de la fonction) sans renvoyer de valeur, et tout bloc catch dans les cadres de pile au-dessus de la fonction intercepte l'exception à la place.

Par conséquent, return falsene s'exécutera jamais.

Essayez de lever manuellement une exception pour comprendre le flux de contrôle:

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

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

    return returnValue == 1;
} finally {
    command.Dispose();
}
Nisarg
la source
5

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

le bloc final serait exécuté, puis exécuterait le retour faux; au fond

C'est la faille dans votre logique car le finallybloc n'attrapera pas l'exception et n'atteindra jamais la dernière instruction de retour.

moiJustAndrew
la source
4

La dernière instruction return falseest inaccessible, car le bloc try n'a pas une catchpartie qui gérerait l'exception, de sorte que l'exception est renvoyée après le finallybloc et l'exécution n'atteint jamais la dernière instruction.

Martin Staufcik
la source
2

Vous avez deux chemins de retour dans votre code, dont le second est inaccessible à cause du premier. La dernière instruction de votre trybloc return returnValue == 1;fournit votre retour normal, vous ne pouvez donc jamais atteindre le return false;à la fin du bloc de méthode.

FWIW, l'ordre d'exection lié au finallybloc est: l'expression fournissant la valeur de retour dans le bloc try sera évaluée en premier, puis le bloc finally sera exécuté, puis la valeur d'expression calculée sera retournée (à l'intérieur du bloc try).

Concernant le flux sur exception ... sans a catch, le finallysera exécuté sur exception avant que l'exception ne soit ensuite renvoyée hors de la méthode; il n'y a pas de chemin de «retour».

C Robinson
la source