Try / Then (sans le Catch) fera-t-il apparaître l'exception?

115

Je suis presque sûr que la réponse est OUI. Si j'utilise un bloc Try Enfin mais n'utilise pas de bloc Catch, toutes les exceptions apparaîtront. Correct?

Des réflexions sur la pratique en général?

Seth

Seth Spearman
la source

Réponses:

130

Oui, il le sera absolument. En supposant que votre finallybloc ne lève pas d'exception, bien sûr, auquel cas cela "remplacera" effectivement celui qui a été lancé à l'origine.

Jon Skeet
la source
13
@David: Vous ne pouvez pas revenir d'un bloc finally en C #.
Jon Skeet
3
La documentation msdn confirme également cette réponse: 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.
haut débit
60

Des réflexions sur la pratique en général?

Oui. Soyez prudent . Lorsque votre bloc finally est en cours d'exécution, il est tout à fait possible qu'il soit en cours d'exécution car une exception inattendue et non gérée a été levée . Cela signifie que quelque chose est cassé et que quelque chose de complètement inattendu peut se produire.

Dans cette situation, il est probable que vous ne devriez pas du tout exécuter de code dans les blocs finally. Le code du bloc finally peut être construit pour supposer que les sous-systèmes dont il dépend sont sains, alors qu'en fait ils pourraient être profondément endommagés. Le code dans le bloc finally pourrait aggraver les choses.

Par exemple, je vois souvent ce genre de chose:

DisableAccessToTheResource();
try
{
    DoSomethingToTheResource();
}
finally
{
    EnableAccessToTheResource();
}

L'auteur de ce code pense: "Je suis en train de faire une mutation temporaire de l'état du monde; j'ai besoin de restaurer l'état à ce qu'il était avant d'être appelé". Mais réfléchissons à toutes les façons dont cela pourrait mal tourner.

Premièrement, l'accès à la ressource peut déjà être désactivé par l'appelant; dans ce cas, ce code le réactive, éventuellement prématurément.

Deuxièmement, si DoSomethingToTheResource lève une exception, est-ce la bonne chose à faire pour permettre l'accès à la ressource ??? Le code qui gère la ressource est rompu de manière inattendue . Ce code dit, en effet, "si le code de gestion est cassé, assurez-vous qu'un autre code peut appeler ce code cassé dès que possible, afin qu'il puisse échouer horriblement aussi ." Cela semble être une mauvaise idée.

Troisièmement, si DoSomethingToTheResource lève une exception, comment savons-nous que EnableAccessToTheResource ne lèvera pas également une exception? Quelle que soit la gravité de l'utilisation de la ressource, elle peut également affecter le code de nettoyage, auquel cas l'exception d'origine sera perdue et le problème sera plus difficile à diagnostiquer.

J'ai tendance à écrire du code comme celui-ci sans utiliser de blocs try-finally:

bool wasDisabled = IsAccessDisabled();
if (!wasDisabled)
    DisableAccessToTheResource();
DoSomethingToTheResource();
if (!wasDisabled)
    EnableAccessToTheResource();

Maintenant, l'état n'est pas muté à moins qu'il ne soit nécessaire. Maintenant, l'état de l'appelant n'est pas perturbé. Et maintenant, si DoSomethingToTheResource échoue, nous ne réactivons pas l'accès. Nous supposons que quelque chose est profondément brisé et ne risque pas d'aggraver la situation en essayant de continuer à exécuter du code. Laissez l'appelant s'occuper du problème, s'il le peut.

Alors, quand est-ce une bonne idée d'exécuter un bloc finally? Premièrement, lorsque l'exception est attendue. Par exemple, vous pouvez vous attendre à ce qu'une tentative de verrouillage d'un fichier échoue, car quelqu'un d'autre l'a verrouillé. Dans ce cas, il est judicieux d'attraper l'exception et de la signaler à l'utilisateur. Dans ce cas, l'incertitude sur ce qui est cassé est réduite; il est peu probable que vous empireriez les choses en nettoyant.

Deuxièmement, lorsque la ressource que vous nettoyez est une ressource système rare. Par exemple, il est judicieux de fermer un descripteur de fichier dans un bloc finally. (Une "utilisation" est bien sûr juste une autre façon d'écrire un bloc try-finally.) Le contenu du fichier est peut-être corrompu, mais vous ne pouvez rien y faire maintenant. Le descripteur de fichier va finir par être fermé, donc cela pourrait aussi bien être le plus tôt possible.

Eric Lippert
la source
7
Encore plus d'exemples de la façon dont nous n'avons pas encore vraiment agi ensemble en tant qu'industrie en matière de gestion des erreurs. Non pas que j'aie quelque chose de mieux à suggérer que des exceptions, mais j'espère que l'avenir nous réserve quelque chose de plus susceptible de conduire à la bonne marche à suivre assez facilement.
Jon Skeet
Dans vb.net, il est possible pour le code d'un bloc Enfin de savoir quelle exception est en attente. Si l'on met en place une hiérarchie d'exceptions pour distinguer "ne l'a pas fait; état sinon ok" de "l'état est cassé", on pourrait avoir un bloc Final uniquement si l'exception en attente n'est pas l'une des mauvaises. J'aime que les routines d'élimination / nettoyage attrapent les exceptions et lancent une DisposerFailedException (qui est dans la catégorie "mauvaise" et inclut l'exception d'origine comme InnerException). J'aimerais voir une interface iDisposableEx standard avec un Dispose (Ex as Exception) pour faciliter cela.
supercat
Bien que j'apprécie qu'il existe des cas où une exception peut se produire alors qu'un objet est dans un mauvais état et qu'une tentative de nettoyage aggravera les choses, je pense que ces problèmes devraient être traités dans le code de nettoyage, éventuellement avec l'aide de délégués. Un wrapper de verrouillage, par exemple, pourrait exposer un délégué de fonction «CheckIfSafeToUnlock (Ex as Exception)» qui pourrait être défini à des moments où l'objet verrouillé est dans un état non valide et effacé dans le cas contraire. Avant de libérer le verrou, le wrapper pouvait vérifier le délégué; s'il n'est pas vide, il pourrait l'exécuter et libérer le verrou uniquement s'il renvoie true.
supercat
Le fait qu'un wrapper utilise un tel délégué permettrait au code qui a manipulé l'état de l'objet de sélectionner l'action de nettoyage appropriée s'il est perturbé. De telles actions peuvent inclure la réparation de la structure de données et le retour de True, le lancement d'une autre exception "plus sévère" qui encapsule celle d'origine, ou le fait de laisser l'exception d'origine percoler tout en renvoyant False.
supercat
2
Eric, merci pour votre excellente réponse. J'imagine que j'aurais dû poser deux questions parce que vous et Jon avez raison. Dans tous les cas, vous êtes voté pour. Merci pour votre attention à la question.
Seth Spearman