Pourquoi essayer {…} enfin {…} bien; essayer {…} attraper {} mal?

201

J'ai vu des gens dire que c'est une mauvaise forme d'utiliser catch sans argument, surtout si ce catch ne fait rien:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
catch   // No args, so it will catch any exception
{}
reader.Close();

Cependant, cela est considéré comme une bonne forme:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
finally   // Will execute despite any exception
{
  reader.Close();
}

Pour autant que je sache, la seule différence entre mettre du code de nettoyage dans un bloc finally et mettre du code de nettoyage après les blocs try..catch est si vous avez des instructions de retour dans votre bloc try (dans ce cas, le code de nettoyage dans exécuter, mais le code après le try..catch ne sera pas).

Sinon, qu'est-ce qui est si spécial finalement?

mbeckish
la source
7
Avant d'essayer d'attraper un tigre que vous ne pouvez pas gérer, vous devez documenter vos souhaits Enfin.
Le sujet des exceptions dans la documentation peut donner de bonnes informations. Jetez également un œil à l' exemple de Final Block .
Athafoud

Réponses:

357

La grande différence est que cela try...catchva avaler l'exception, cachant le fait qu'une erreur s'est produite. try..finallyexécutera votre code de nettoyage, puis l'exception continuera, à gérer par quelque chose qui sait quoi en faire.

Khoth
la source
11
Tout code écrit en gardant à l'esprit l'encapsulation ne pourra probablement gérer l'exception qu'au point où elle est déclenchée. Le simple fait de le remonter dans la pile des appels dans l'espoir désespéré que quelque chose d'autre puisse gérer une exception arbitraire est une recette pour un désastre.
David Arno
3
Dans la plupart des cas, il est plus évident pourquoi une exception particulière se produirait au niveau de l'application (par exemple, un certain paramètre de configuration) qu'au niveau du librray de classe.
Mark Cidade
88
David - Je préférerais que le programme échoue rapidement afin que je puisse être informé du problème plutôt que de laisser le programme en cours d'exécution dans un état inconnu.
Erik Forbes,
6
Si votre programme est dans un état inconnu après une exception, vous faites le mauvais code.
Zan Lynx
41
@DavidArno, tout code écrit en pensant à l'encapsulation ne doit gérer que les exceptions dans leur portée. Tout le reste doit être transmis à quelqu'un d'autre. Si j'ai une application qui obtient un nom de fichier des utilisateurs puis lit le fichier, et mon lecteur de fichier obtient une exception ouvrant le fichier, il doit les transmettre (ou consommer l'exception et en jeter une nouvelle) afin que l'application puisse dire , hé - le fichier ne s'est pas ouvert, demandons à l'utilisateur d'en créer un autre. Le lecteur de fichiers ne doit pas être en mesure d'inviter les utilisateurs ou de prendre d'autres mesures en réponse. Son seul but est de lire des fichiers.
iheanyi
62

"Enfin" est une déclaration de "quelque chose que vous devez toujours faire pour vous assurer que l'état du programme est sain". En tant que tel, il est toujours bon d'en avoir un, s'il y a une possibilité que des exceptions puissent perturber l'état du programme. Le compilateur va également très loin pour s'assurer que votre code Enfin est exécuté.

"Catch" est une déclaration de "Je peux récupérer de cette exception". Vous ne devez récupérer que des exceptions que vous pouvez vraiment corriger - capture sans arguments dit "Hé, je peux récupérer de tout!", Ce qui est presque toujours faux.

S'il était possible de se remettre de chaque exception, alors ce serait vraiment un petit problème sémantique sur ce que vous déclarez être votre intention. Cependant, ce n'est pas le cas et les cadres au-dessus du vôtre seront certainement mieux équipés pour gérer certaines exceptions. En tant que tel, utilisez enfin, exécutez votre code de nettoyage gratuitement, mais laissez encore plus de gestionnaires compétents gérer le problème.

Adam Wright
la source
1
Votre sentiment est répandu, mais ignore malheureusement un autre cas important: invalider expressément un objet dont les invariants peuvent ne plus tenir. Un modèle courant est que le code acquière un verrou, apporte des modifications à un objet et relâche le verrou. Si une exception se produit après avoir effectué certaines mais pas toutes les modifications, l'objet peut être laissé dans un état non valide. Même si à mon humble avis de meilleures alternatives devraient exister, je ne connais pas de meilleure approche que de capturer toute exception qui se produit alors que l'état de l'objet peut être invalide, invalider expressément l'état et recommencer.
supercat
32

Parce que lorsque cette seule ligne lève une exception, vous ne le saurez pas.

Avec le premier bloc de code, l'exception sera simplement absorbée , le programme continuera à s'exécuter même lorsque l'état du programme pourrait être incorrect.

Avec le deuxième bloc, l'exception sera levée et bouillonnera mais le fonctionnement reader.Close()est toujours garanti.

Si une exception n'est pas attendue, alors ne mettez pas de bloc try..catch juste ainsi, il sera difficile de déboguer plus tard lorsque le programme est entré dans un mauvais état et vous ne savez pas pourquoi.

chakrit
la source
21

Enfin est exécuté quoi qu'il arrive. Donc, si votre bloc try a réussi, il s'exécutera, si votre bloc try échoue, il exécutera alors le bloc catch, puis le bloc finally.

En outre, il est préférable d'essayer d'utiliser la construction suivante:

using (StreamReader reader=new  StreamReader("myfile.txt"))
{
}

Comme l'instruction using est automatiquement encapsulée dans un essai / enfin et le flux sera automatiquement fermé. (Vous devrez mettre un try / catch autour de l'instruction using si vous voulez réellement attraper l'exception).

Mark Ingram
la source
5
Ce n'est pas correct. L'utilisation n'encapsule pas le code avec try / catch, cela devrait dire try / finalement
pr0nin
8

Bien que les 2 blocs de code suivants soient équivalents, ils ne sont pas égaux.

try
{
  int i = 1/0; 
}
catch
{
  reader.Close();
  throw;
}

try
{
  int i = 1/0;
}
finally
{
  reader.Close();
}
  1. «enfin» est un code révélateur d'intentions. Vous déclarez au compilateur et aux autres programmeurs que ce code doit s'exécuter quoi qu'il arrive.
  2. si vous avez plusieurs blocs catch et que vous avez du code de nettoyage, vous en avez enfin besoin. Sans finalement, vous dupliqueriez votre code de nettoyage dans chaque bloc catch. (Principe SEC)

enfin les blocs sont spéciaux. Le CLR reconnaît et traite le code avec un bloc finally séparément des blocs catch, et le CLR va très loin pour garantir qu'un bloc finally s'exécutera toujours. Ce n'est pas seulement du sucre syntaxique du compilateur.

Robert Paulson
la source
5

Je suis d'accord avec ce qui semble être le consensus ici - une «capture» vide est mauvaise car elle masque toute exception qui aurait pu se produire dans le bloc try.

En outre, du point de vue de la lisibilité, lorsque je vois un bloc «essayer», je suppose qu'il y aura une déclaration «catch» correspondante. Si vous utilisez uniquement un «essai» afin de vous assurer que les ressources sont désallouées dans le bloc «finalement», vous pouvez plutôt considérer l' instruction «using» :

using (StreamReader reader = new StreamReader('myfile.txt'))
{
    // do stuff here
} // reader.dispose() is called automatically

Vous pouvez utiliser l'instruction 'using' avec n'importe quel objet qui implémente IDisposable. La méthode dispose () de l'objet est appelée automatiquement à la fin du bloc.

Chris Lawlor
la source
4

Utilisez Try..Catch..Finally, si votre méthode sait comment gérer l'exception localement. L'exception se produit dans Try, Handled in Catch et après ce nettoyage est effectué dans Finalement.

Si votre méthode ne sait pas comment gérer l'exception mais a besoin d'un nettoyage une fois qu'elle s'est produite, utilisez Try..Finally

Par cela, l'exception est propagée aux méthodes d'appel et gérée s'il existe des instructions Catch appropriées dans les méthodes d'appel. S'il n'y a pas de gestionnaire d'exceptions dans la méthode actuelle ou l'une des méthodes d'appel, l'application se bloque.

Par Try..Finallyelle est assurée que le nettoyage local est fait avant de propager l'exception aux méthodes d'appel.

manjuv
la source
1
Aussi basique que soit cette réponse, c'est absolument la meilleure. Il est bon d'avoir l'habitude d'essayer / attraper / enfin, même si l'un des deux derniers est laissé vide. Il y a des circonstances TRÈS RARES où un bloc catch peut exister et être vide, mais au moins si vous écrivez toujours try / catch / enfin, vous verrez le bloc vide pendant que vous parcourez le code. Avoir un bloc finalement vide est utile de la même manière. Si vous avez besoin d'un nettoyage plus tard, ou si vous devez déboguer un état au moment de l'exception, c'est incroyablement utile.
Jesse Williams
3

Le bloc try..finally lèvera toujours toutes les exceptions levées. Toutfinally suffit de s'assurer que le code de nettoyage est exécuté avant que l'exception ne soit levée.

Le try..catch avec un catch vide consommera complètement toute exception et masquera le fait que cela s'est produit. Le lecteur sera fermé, mais on ne sait pas si la bonne chose s'est produite. Et si votre intention était d'écrire i dans le fichier? Dans ce cas, vous ne pourrez pas accéder à cette partie du code et monfichier.txt sera vide. Toutes les méthodes en aval gèrent-elles correctement cela? Lorsque vous verrez le fichier vide, pourrez-vous deviner correctement qu'il est vide car une exception a été levée? Mieux vaut lever l'exception et faire savoir que vous faites quelque chose de mal.

Une autre raison est que le try..catch fait comme ceci est complètement incorrect. Ce que vous dites en faisant cela, c'est: «Peu importe ce qui se passe, je peux le gérer.» Qu'en est-il StackOverflowException, pouvez-vous nettoyer après cela? Et alors OutOfMemoryException? En général, vous ne devez gérer que les exceptions que vous attendez et savoir comment gérer.

OwenP
la source
2

Si vous ne savez pas quel type d'exception intercepter ou quoi en faire, il est inutile d'avoir une instruction catch. Vous devriez simplement le laisser à un appelant supérieur qui peut avoir plus d'informations sur la situation pour savoir quoi faire.

Vous devriez toujours avoir une instruction finally là-dedans au cas où il y aurait une exception, afin que vous puissiez nettoyer les ressources avant que cette exception ne soit levée à l'appelant.

Mark Cidade
la source
2

Du point de vue de la lisibilité, il est plus explicite de dire aux futurs lecteurs de code "ce genre de choses ici est important, cela doit être fait quoi qu'il arrive." C'est bon.

De plus, les déclarations de captures vides ont tendance à avoir une certaine "odeur". Ils peuvent être un signe que les développeurs ne réfléchissent pas aux diverses exceptions qui peuvent survenir et à la façon de les gérer.

Ryan
la source
2

Enfin est facultatif - il n'y a aucune raison d'avoir un bloc "Enfin" s'il n'y a pas de ressources à nettoyer.

Guy Starbuck
la source
2

Extrait de: ici

La levée et la capture d'exceptions ne devraient pas se produire régulièrement dans le cadre de l'exécution réussie d'une méthode. Lors du développement de bibliothèques de classes, le code client doit avoir la possibilité de tester une condition d'erreur avant d'entreprendre une opération pouvant entraîner le déclenchement d'une exception. Par exemple, System.IO.FileStream fournit une propriété CanRead qui peut être vérifiée avant d'appeler la méthode Read, empêchant une exception potentielle d'être déclenchée, comme illustré dans l'extrait de code suivant:

Dim str As Stream = GetStream () If (str.CanRead) Then 'code pour lire le flux End If

La décision de vérifier ou non l'état d'un objet avant d'invoquer une méthode particulière susceptible de déclencher une exception dépend de l'état attendu de l'objet. Si un objet FileStream est créé à l'aide d'un chemin de fichier qui doit exister et d'un constructeur qui doit retourner un fichier en mode lecture, la vérification de la propriété CanRead n'est pas nécessaire; l'incapacité de lire le FileStream serait une violation du comportement attendu des appels de méthode effectués et une exception devrait être levée. En revanche, si une méthode est documentée comme renvoyant une référence FileStream qui peut ou non être lisible, il est conseillé de vérifier la propriété CanRead avant de tenter de lire les données.

Pour illustrer l'impact sur les performances que l'utilisation d'une technique de codage "exécuter jusqu'à exception" peut entraîner, les performances d'un cast, qui lève une InvalidCastException si le cast échoue, sont comparées à l'opérateur C # en tant qu'opérateur, qui renvoie des valeurs nulles en cas d'échec d'un cast. Les performances des deux techniques sont identiques dans le cas où le plâtre est valide (voir test 8.05), mais dans le cas où le plâtre n'est pas valide et l'utilisation d'un plâtre provoque une exception, l'utilisation d'un plâtre est 600 fois plus lente que l'utilisation du comme opérateur (voir Test 8.06). L'impact de haute performance de la technique de levée d'exception inclut le coût d'allocation, de levée et de capture de l'exception et le coût de la récupération de place ultérieure de l'objet d'exception, ce qui signifie que l'impact instantané du lancement d'une exception n'est pas si élevé. Comme plus d'exceptions sont levées,

SpoiledTechie.com
la source
2
Scott - si le texte que vous avez cité ci-dessus se trouve derrière le mur de paiement de expertsexchange.com, vous ne devriez probablement pas le publier ici. Je me trompe peut-être, mais je parierais que ce n'est pas une bonne idée.
Onorio Catenacci,
2

C'est une mauvaise pratique d'ajouter une clause catch juste pour annuler l'exception.

Bastien Vandamme
la source
2

Si vous lisez C # pour les programmeurs, vous comprendrez que le bloc finally a été conçu pour optimiser une application et éviter les fuites de mémoire.

Le CLR n'élimine pas complètement les fuites ... des fuites de mémoire peuvent se produire si le programme garde par inadvertance des références à des objets indésirables

Par exemple, lorsque vous ouvrez une connexion de fichier ou de base de données, votre ordinateur allouera de la mémoire pour traiter cette transaction, et cette mémoire ne sera conservée que si la commande supprimée ou fermée a été exécutée. mais si au cours de la transaction, une erreur s'est produite, la commande en cours se terminera, sauf si elle était à l'intérieur du try.. finally..bloc.

catchétait différent finallydans le sens où, catch a été conçu pour vous permettre de gérer / gérer ou d'interpréter l'erreur elle-même. Considérez-le comme une personne qui vous dit "hé j'ai attrapé des méchants, que voulez-vous que je leur fasse?" while a finallyété conçu pour vous assurer que vos ressources étaient correctement placées. Pensez à quelqu'un qui, qu'il y ait ou non des méchants, s'assurera que votre propriété est toujours en sécurité.

Et vous devriez permettre à ces deux-là de travailler ensemble pour de bon.

par exemple:

try
{
  StreamReader reader=new  StreamReader("myfile.txt");
  //do other stuff
}
catch(Exception ex){
 // Create log, or show notification
 generic.Createlog("Error", ex.message);
}
finally   // Will execute despite any exception
{
  reader.Close();
}
dr.Crow
la source
1

Avec enfin, vous pouvez nettoyer les ressources, même si votre instruction catch lève l'exception au programme appelant. Avec votre exemple contenant l'instruction catch vide, il y a peu de différence. Cependant, si dans votre capture, vous effectuez un traitement et lancez l'erreur, ou même si vous n'avez même pas de capture du tout, le fichier sera toujours exécuté.

Kibbee
la source
1

Eh bien, pour commencer, c'est une mauvaise pratique d'attraper des exceptions que vous ne prenez pas la peine de gérer. Consultez le chapitre 5 sur les performances .Net à partir de l' amélioration des performances et de l'évolutivité des applications .NET . Remarque: vous devriez probablement charger le flux à l'intérieur du bloc try, de cette façon, vous pouvez intercepter l'exception pertinente en cas d'échec. La création du flux en dehors du bloc try va à l'encontre de son objectif.

Facteur mystique
la source
0

Parmi probablement de nombreuses raisons, les exceptions sont très lentes à exécuter. Vous pouvez facilement paralyser vos temps d'exécution si cela se produit souvent.

lotsoffreetime
la source
0

Le problème avec les blocs try / catch qui interceptent toutes les exceptions est que votre programme est maintenant dans un état indéterminé si une exception inconnue se produit. Cela va complètement à l'encontre de la règle d'échec rapide - vous ne voulez pas que votre programme continue si une exception se produit. L'essai / capture ci-dessus intercepterait même OutOfMemoryExceptions, mais c'est certainement un état dans lequel votre programme ne fonctionnera pas.

Les blocs try / finally vous permettent d'exécuter du code de nettoyage tout en échouant rapidement. Dans la plupart des cas, vous souhaitez uniquement intercepter toutes les exceptions au niveau global, afin de pouvoir les journaliser, puis quitter.

David Mohundro
la source
0

La différence effective entre vos exemples est négligeable tant qu'aucune exception n'est levée.

Si, cependant, une exception est levée dans la clause 'try', le premier exemple l'avalera complètement. Le deuxième exemple lèvera l'exception à l'étape suivante de la pile d'appels, de sorte que la différence dans les exemples indiqués est que l'un masque complètement toutes les exceptions (premier exemple), et l'autre (deuxième exemple) conserve les informations d'exception pour une gestion ultérieure potentielle tout en exécutant toujours le contenu de la clause «enfin».

Si, par exemple, vous deviez mettre du code dans la clause 'catch' du premier exemple qui a levé une exception (soit celle qui a été déclenchée initialement, soit une nouvelle), le code de nettoyage du lecteur ne s'exécutera jamais. S'exécute enfin indépendamment de ce qui se passe dans la clause «catch».

Ainsi, la principale différence entre «catch» et «finalement» est que le contenu du bloc «enfin» (à quelques rares exceptions près) peut être considéré comme garanti pour s'exécuter, même face à une exception inattendue, tandis que tout code suivant une clause «catch» (mais en dehors d'une clause «finalement») ne comporterait pas une telle garantie.

Soit dit en passant, Stream et StreamReader implémentent tous deux IDisposable et peuvent être encapsulés dans un bloc `` using ''. 'Utiliser' les blocs est l'équivalent sémantique de try / finally (pas de 'catch'), donc votre exemple pourrait être exprimé de manière plus laconique:

using (StreamReader reader = new  StreamReader("myfile.txt"))
{
  int i = 5 / 0;
}

... qui fermera et supprimera l'instance StreamReader lorsqu'elle sortira du cadre. J'espère que cela t'aides.

Jared
la source
0

essayer {…} catch {} n'est pas toujours mauvais. Ce n'est pas un modèle courant, mais j'ai tendance à l'utiliser lorsque j'ai besoin d'arrêter des ressources, quoi qu'il arrive, comme fermer un (éventuellement) sockets ouverts à la fin d'un thread.

Martin Liesén
la source