web-dev-qa-db-fra.com

Code inaccessible, mais accessible avec une exception

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 renvoie true.

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

command.ExecuteNonQuery() est documenté pour lancer un Invalid​Operation​Exception lorsque "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 bloc try s'arrêterait, le bloc finally serait exécuté, puis exécuterait le return false; au fond.

Cependant, mon IDE prétend que le return false; est un code inaccessible. Et cela semble être vrai, je peux le supprimer et il compile sans aucune plainte. Cependant, pour moi, il semble qu'il n'y aurait pas de valeur de retour pour le chemin de 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?

108
0xCAFEBABE

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 que le Compilateur comprend suffisamment par Analyse statique qu'il ne peut pas être atteint et l'omet complètement du compilé IL (d'où votre avertissement)

Remarque: Vous pouvez prouver ce fait à vous-même en essayant de passer au code inaccessible avec le débogueur, ou en utilisant un IL Explorer

Le finally peut s'exécuter sur une Exception, (bien que cela mis à part) cela ne change pas le fait (dans ce cas) ce sera toujours une Exception non interceptée . Ergo, le dernier return ne sera jamais touché malgré tout.

  • Si vous voulez que le code continue sur le dernier return, votre seule option est de Catch le Exception;

  • Si vous ne le faites pas, laissez-le tel quel et supprimez le 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 qui sont 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 finalement s'exécutent lorsque le contrôle quitte une instruction try. Le transfert de contrôle peut se produire à la suite de l'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, le bloc finally associé est garanti d'être exécuté. Cependant, si l'exception n'est pas gérée, l'exécution du bloc finally dépend de la façon dont l'opération de déroulement de l'exception est déclenchée. Cela dépend à son tour de la configuration de votre ordinateur.

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 . Alternativement, vous pouvez intercepter l'exception qui pourrait être levée dans le bloc try d'une instruction try-finally plus haut dans la pile des 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 des appels. Si l'exception n'est pas interceptée, l'exécution du bloc finally dépend de la décision du système d'exploitation de déclencher une opération de déroulement d'exception.

Enfin

Lorsque vous utilisez quelque chose qui prend en charge l'interface IDisposable (conçue pour libérer des ressources non gérées), vous pouvez l'encapsuler dans une instruction using . Le compilateur va générer un try {} finally {} Et appeler en interne Dispose() sur l'objet

149
Michael Randall

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

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

Si vous souhaitez que l'exception soit avalée, vous devez utiliser un bloc catch sans throw.

86
Patrick Hofman

L'avertissement est dû au fait que vous n'avez pas utilisé catch et que votre méthode est essentiellement écrite comme ceci:

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

Puisque vous utilisez finally uniquement pour éliminer, la solution préférée consiste à utiliser le modèle using:

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

C'est suffisant pour s'assurer que Dispose sera appelé. Il est garanti d'être appelé soit après l'exécution réussie du bloc de code, soit sur (avant) certains catch down dans la pile d'appels (les appels parents sont en panne, non?).

S'il ne s'agit pas de disposer, alors

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

est suffisant, puisque vous ne devrez jamais retourner false à la fin de la méthode (il n'y a pas besoin de cette ligne). Votre méthode retourne soit le résultat de l'exécution de la commande (true ou false), soit elle lèvera une exception sinon.


Pensez également à lever vos propres exceptions en encapsulant les exceptions attendues (consultez 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 que finally est appelé même si l'exception n'est pas gérée. Dans ce cas, il vous suffit de l'attraper vous-même et de relancer (voir cette réponse ):

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

Il semble que vous cherchiez 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 finallyne pas avaler toute exception

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

// And this code will never be reached
13
Dmitry Bychenko

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

le bloc finalement 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.

Les blocs finally 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 blocs catch sont capables de détecter des exceptions.

Lecture de la documentation ,

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 . Alternativement, vous pouvez intercepter l'exception qui pourrait être levée dans le bloc try d'une instruction try-finally plus haut dans la pile des 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 des appels. Si l'exception n'est pas interceptée, l'exécution du bloc finally dépend de la décision du système d'exploitation de déclencher une opération de déroulement d'exception .

Cela montre clairement que le finalement n'est pas destiné à intercepter l'exception, et vous auriez été correct s'il y avait eu une instruction catch vide avant l'instruction finally.

8
Ray Wu

Lorsque l'exception est levée, la pile se déroule (l'exécution sortira de la fonction) sans renvoyer de valeur, et tout bloc catch dans les cadres de pile au-dessus de la fonction interceptera plutôt l'exception.

Par conséquent, return false ne 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();
}
7
Nisarg

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 finalement serait exécuté, puis exécuterait le retour faux; au fond

Il s'agit de la faille de votre logique car le bloc finally n'attrapera pas l'exception et n'atteindra jamais la dernière instruction de retour.

5
meJustAndrew

La dernière déclaration return false est inaccessible, car il manque dans le bloc try une partie catch qui gérerait l'exception, donc l'exception est renvoyée après le bloc finally et l'exécution n'atteint jamais la dernière instruction.

4
Martin Staufcik

Vous avez deux chemins de retour dans votre code, le second étant inaccessible à cause du premier. La dernière instruction dans votre bloc tryreturn 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 bloc finally est: l'expression fournissant la valeur de retour dans le bloc try sera évaluée en premier, puis le bloc finalement sera exécuté, puis la valeur d'expression calculée sera retournée ( à l'intérieur du bloc try).

En ce qui concerne le flux sur exception ... sans catch, le finally sera exécuté lors d'une exception avant que l'exception ne soit ensuite retirée de la méthode; il n'y a pas de chemin de "retour".

2
C Robinson