Pourquoi attraper et renvoyer une exception en C#?
je regarde l'article C# - Data Transfer Object sur des Dto sérialisables.
l'article comprend ce morceau de code:
public static string SerializeDTO(DTO dto) {
try {
XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
StringWriter sWriter = new StringWriter();
xmlSer.Serialize(sWriter, dto);
return sWriter.ToString();
}
catch(Exception ex) {
throw ex;
}
}
le reste de l'article semble sain et raisonnable (à un noob), mais que try-catch-throw jette une WtfException... n'est-ce pas exactement équivalent à ne pas traiter d'exceptions du tout?
Ergo:
public static string SerializeDTO(DTO dto) {
XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
StringWriter sWriter = new StringWriter();
xmlSer.Serialize(sWriter, dto);
return sWriter.ToString();
}
ou est-ce que je manque quelque chose de fondamental sur le traitement des erreurs dans C#? C'est à peu près la même chose que Java (moins les exceptions vérifiées), n'est-ce pas? ... C'est-à-dire qu'ils ont tous les deux affiné le c++.
Le Dépassement de Pile question La différence entre les re-jeter des paramètres de capture et de ne rien faire? semble à l'appui de mon affirmation selon laquelle try-catch-lancer-un no-op.
EDIT:
juste pour résumer pour tous ceux qui trouvent ce fil à l'avenir...
NE PAS
try {
// Do stuff that might throw an exception
}
catch (Exception e) {
throw e; // This destroys the strack trace information!
}
l'information de trace de pile peut être cruciale pour identifier la cause profonde du problème!
FAIRE
try {
// Do stuff that might throw an exception
}
catch (SqlException e) {
// Log it
if (e.ErrorCode != NO_ROW_ERROR) { // filter out NoDataFound.
// Do special cleanup, like maybe closing the "dirty" database connection.
throw; // This preserves the stack trace
}
}
catch (IOException e) {
// Log it
throw;
}
catch (Exception e) {
// Log it
throw new DAOException("Excrement occurred", e); // wrapped & chained exceptions (just like java).
}
finally {
// Normal clean goes here (like closing open files).
}
Attraper le plus d'exceptions spécifiques avant de le moins spécifiques (comme Java).
références:
16 réponses
tout D'abord, la façon dont le code dans l'article le fait est mal. throw ex
réinitialisera la pile d'appels dans l'exception au point où se trouve cette instruction de lancer; en perdant les informations sur l'endroit où l'exception a réellement été créée.
deuxièmement, si vous attrapez et relancez comme ça, Je ne vois aucune valeur ajoutée, l'exemple de code ci-dessus serait tout aussi bon (ou, étant donné le throw ex
bit, encore mieux) sans le try-catch.
cependant, il y a des cas où vous pourriez vouloir attraper et repenser une exception. L'exploitation forestière pourrait en être une:
try
{
// code that may throw exceptions
}
catch(Exception ex)
{
// add error logging here
throw;
}
ne faites pas ceci,
try
{
...
}
catch(Exception ex)
{
throw ex;
}
vous perdrez l'information de trace de pile...
faire", 151950920"
try { ... }
catch { throw; }
ou
try { ... }
catch (Exception ex)
{
throw new Exception("My Custom Error Message", ex);
}
une des raisons pour lesquelles vous pourriez vouloir rethrow est si vous manipulez différentes exceptions, pour par exemple
try
{
...
}
catch(SQLException sex)
{
//Do Custom Logging
//Don't throw exception - swallow it here
}
catch(OtherException oex)
{
//Do something else
throw new WrappedException("Other Exception occured");
}
catch
{
System.Diagnostics.Debug.WriteLine("Eeep! an error, not to worry, will be handled higher up the call stack");
throw; //Chuck everything else back up the stack
}
C# (avant C# 6) ne supporte pas les" exceptions filtrées " CIL, ce que fait VB, donc dans C# 1-5 une raison pour relancer une exception est que vous n'avez pas assez d'informations au moment de la capture() pour déterminer si vous vouliez réellement attraper l'exception.
par exemple, en VB vous pouvez faire
Try
..
Catch Ex As MyException When Ex.ErrorCode = 123
..
End Try
...qui ne traiterait pas MyExceptions avec des valeurs ErrorCode différentes. Dans C# avant v6, vous auriez à attraper et relancez la MyException si L'ErrorCode n'était pas 123:
try
{
...
}
catch(MyException ex)
{
if (ex.ErrorCode != 123) throw;
...
}
depuis C# 6.0 vous pouvez filtrer comme avec VB:
try
{
// Do stuff
}
catch (Exception e) when (e.ErrorCode == 123456) // filter
{
// Handle, other exceptions will be left alone and bubble up
}
ma principale raison pour avoir un code comme:
try
{
//Some code
}
catch (Exception e)
{
throw;
}
est donc je peux avoir un point de rupture dans la capture, qui a un objet exception instancié. Je le fais souvent pendant le développement / débogage. Bien sûr, le compilateur me donne un avertissement sur tous les e inutilisés, et idéalement ils devraient être retirés avant une construction de version.
ils sont sympas lors du débogage.
une raison valable pour repenser les exceptions peut être que vous voulez ajouter de l'information à l'exception, ou peut-être envelopper l'exception originale dans l'un de vos propres faire:
public static string SerializeDTO(DTO dto) {
try {
XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
StringWriter sWriter = new StringWriter();
xmlSer.Serialize(sWriter, dto);
return sWriter.ToString();
}
catch(Exception ex) {
string message =
String.Format("Something went wrong serializing DTO {0}", DTO);
throw new MyLibraryException(message, ex);
}
}
n'est-ce pas exactement équivalent à pas la gestion des exceptions?
pas exactement, ce n'est pas pareil. Il réinitialise la chaîne de l'exception. Si je suis d'accord que c'est probablement une erreur, et donc un exemple de mauvais code.
vous ne voulez pas jeter ex-comme cela va perdre la pile d'appels. Voir gestion des exceptions (MSDN).
et oui, l'essai...catch ne fait rien d'utile (à part perdre la pile d'appels - donc c'est en fait pire - à moins que pour une raison quelconque vous ne vouliez pas exposer cette information).
un point que les gens n'ont pas mentionné est que, bien que les langues.Net ne font pas vraiment une distinction appropriée, la question de savoir si l'on devrait prendre des mesures lorsqu'une exception se produit, et si l'on va résoudre il, sont en fait des questions distinctes. Il y a de nombreux cas où l'on devrait prendre des mesures fondées sur des exceptions qu'on n'a aucun espoir de résoudre, et il y a certains cas où tout ce qui est nécessaire pour "résoudre" une exception est de décompresser la pile jusqu'à un certain point--aucune autre action requise.
en raison de la sagesse commune que l'on devrait seulement "attraper" les choses que l'on peut "gérer", beaucoup de code qui devrait prendre des mesures en cas d'exceptions se produisent, ne fait pas. Par exemple, beaucoup de code va acquérir une serrure, mettre l'objet gardé "temporairement" dans un État qui viole ses invariants, puis le mettre objet dans un État légitime, et puis relâcher la serrure en arrière avant que n'importe qui d'autre puisse voir l'objet. Si une exception se produit pendant que l'objet est dans un État dangereusement-invalide, la pratique courante est de libérer la serrure avec l'objet encore dans cet état. Un modèle beaucoup mieux serait d'avoir une exception qui se produit alors que l'objet est dans une condition "dangereuse" invalider expressément la serrure de sorte que toute tentative future de l'acquérir sera immédiatement échec. L'utilisation cohérente d'un tel modèle améliorerait grandement la sécurité de la manipulation dite" Pokemon " exception, qui IMHO obtient une mauvaise réputation principalement à cause du code qui permet des exceptions à percer vers le haut sans prendre les mesures appropriées d'abord.
dans la plupart des langues.net, la seule façon pour le code de prendre des mesures basées sur une exception est de catch
(même s'il sait qu'il ne va pas résoudre l'exception), effectuer l'action en question et ensuite re - throw
). Une autre approche possible si le code ne se soucie pas de quelle exception est lancée est d'utiliser un drapeau ok
avec un try/finally
bloc; placer le drapeau ok
à false
avant le bloc, et à true
avant la sortie du bloc, et avant tout return
qui est dans le bloc. Ensuite , dans finally
, supposons que si ok
n'est pas défini, une exception doit s'être produite. Une telle approche est sémantiquement meilleure qu'un catch
/ throw
, mais elle est laide et moins supportable qu'elle ne le devrait.
L'une des raisons possibles de lancer un catch est de désactiver les filtres d'exception situés plus en amont de la pile, afin de les empêcher de filtrer vers le bas ( random old link ). Mais bien sûr, si c'était l'intention, il y aurait un commentaire en le disant.
cela dépend de ce que vous faites dans le bloc catch, et si vous voulez transmettre l'erreur au code appelant ou non.
vous pourriez dire Catch io.FileNotFoundExeption ex
et ensuite utiliser un chemin de fichier alternatif ou un tel, mais tout de même jeter l'erreur sur.
aussi en faisant Throw
au lieu de Throw Ex
vous permet de garder la trace complète de la pile. Throw ex redémarre la trace stack de la déclaration throw (j'espère que cela a du sens).
alors que beaucoup d'autres réponses fournissent de bons exemples de pourquoi vous pourriez vouloir attraper une exception rethrow, personne ne semble avoir mentionné un scénario 'enfin'.
un exemple de ceci est où vous avez une méthode dans laquelle vous mettez le curseur (par exemple à un curseur d'attente), la méthode a plusieurs points de sortie (par exemple si () retour;) et vous voulez vous assurer que le curseur est réinitialisé à la fin de la méthode.
pour ce faire, vous pouvez envelopper tous les code dans un try/catch/finally. Dans la règle finale, le curseur retourne vers le curseur de droite. Pour que vous n'enterriez aucune exception valable, repensez-la dans la prise.
try
{
Cursor.Current = Cursors.WaitCursor;
// Test something
if (testResult) return;
// Do something else
}
catch
{
throw;
}
finally
{
Cursor.Current = Cursors.Default;
}
cela peut être utile lorsque vos fonctions de programmation pour une bibliothèque ou dll.
cette structure rethrow peut être utilisée pour réinitialiser volontairement la pile d'appels de sorte qu'au lieu de voir l'exception lancée d'une fonction individuelle à l'intérieur de la fonction, vous obtenez l'exception de la fonction elle-même.
je pense que c'est juste utilisé pour que les exceptions jetées soient plus propres et n'aillent pas dans les" racines " de la bibliothèque.
dans l'exemple dans le code que vous avez posté, il n'y a, en fait, aucun point pour attraper l'exception car il n'y a rien fait sur la capture il est juste re-thown, en fait il fait plus de mal que bon que la pile d'appel est perdue.
vous, cependant attraper une exception à faire une certaine logique (par exemple la fermeture de la connexion sql de verrouillage de fichier, ou juste quelques journalisation) dans le cas d'une exception le jeter en arrière au code d'appel à traiter. Ce serait plus courant dans une couche d'affaires que le code front end comme vous pouvez vouloir le codeur mettant en œuvre votre couche d'affaires pour gérer l'exception.
pour réitérer bien qu'il n'y ait aucun point à attraper l'exception dans l'exemple que vous avez posté. NE PAS le faire comme ça!
désolé, mais de nombreux exemples de "design amélioré" sentent encore horriblement ou peuvent être extrêmement trompeurs. Ayant try { } catch { journal; jeter } est juste tout à fait inutile. La journalisation d'Exception doit être faite à la place centrale à l'intérieur de l'application. de toute façon, les exceptions se bousculent dans la chaîne de stockage, pourquoi ne pas les enregistrer quelque part près des frontières du système?
attention devrait être utilisée lorsque vous sérialisez votre contexte (i.e. DTO dans un exemple donné) juste dans le log message. Il peut facilement contenir des informations sensibles, on peut ne pas arriver les mains de toutes les personnes qui peuvent accéder aux fichiers journaux. Et si vous n'ajoutez aucune nouvelle information à l'exception, Je ne vois vraiment pas l'intérêt de l'emballage d'exception. Bon vieux Java a un certain point pour cela, il exige appelant de savoir quel type d'exceptions on devrait s'attendre puis appeler le code. Puisque vous n'avez pas ça dans .NET, emballer ne fait pas de bien sur au moins 80% des cas que j'ai vu.
en plus de ce que les autres ont dit, voir my answer à une question connexe qui montre que la capture et la remise en question n'est pas un non-op (c'est en VB, mais une partie du code pourrait être c# invoqué à partir de VB).
la Plupart des réponses à parler de scénario fourre-journal-relever.
au lieu de l'écrire dans votre code envisager d'utiliser AOP, en particulier Postsharp.Diagnostic.Boîte à outils avec Uneexceptionoptions IncludeParameterValue et Y compris cet argument