web-dev-qa-db-fra.com

Pourquoi attraper et rediffuser une exception en C #?

Je regarde l'article C # - Objet de transfert de données sur les 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 (pour un noob), mais ce try-catch-throw jette une exception WtfException ... N'est-ce pas exactement équivalent à ne pas gérer les exceptions?

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 me manque-t-il quelque chose de fondamental concernant la gestion des erreurs en C #? C'est à peu près la même chose que Java (sauf les exceptions vérifiées), n'est-ce pas? ... c'est-à-dire qu'ils ont tous deux affiné le C++.

La question du débordement de pile La différence entre re-lancer une capture sans paramètre et ne rien faire? semble corroborer mon affirmation selon laquelle try-catch-throw est -un non-op.


EDIT:

Juste pour résumer pour quiconque trouve 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!
}

Les informations de trace de pile peuvent être cruciales pour identifier la cause première du problème!

DO

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).
}

Attrapez les exceptions plus spécifiques avant les moins spécifiques (tout comme Java).


Références:

515
corlettk

Première; la façon dont le code dans l'article le fait est mal. throw ex réinitialisera la pile d'appels dans l'exception jusqu'au point où cette instruction de rejet est; perdre l'information sur l'endroit où l'exception a é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 bit throw ex, encore mieux) sans le try-catch.

Cependant, dans certains cas, vous pouvez vouloir capturer et rediffuser une exception. L’exploitation forestière pourrait être l’un d’eux:

try 
{
    // code that may throw exceptions    
}
catch(Exception ex) 
{
    // add error logging here
    throw;
}
404
Fredrik Mörk

Ne fais pas ça,

try 
{
...
}
catch(Exception ex)
{
   throw ex;
}

Vous perdrez les informations de trace de la pile ...

Soit faire

try { ... }
catch { throw; }

OR

try { ... }
catch (Exception ex)
{
    throw new Exception("My Custom Error Message", ex);
}

Une des raisons pour laquelle vous voudrez peut-être réémettre est si vous gérez différentes exceptions, 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
}
112
Eoin Campbell

C # (avant C # 6) ne prend pas en charge les "exceptions filtrées" CIL, ce que VB prend en charge. Ainsi, en C # 1-5, une des raisons pour renvoyer une exception est que vous ne disposez pas de suffisamment d'informations le moment de la capture () pour déterminer si vous vouliez réellement capturer l'exception.

Par exemple, dans VB vous pouvez faire

Try
 ..
Catch Ex As MyException When Ex.ErrorCode = 123
 .. 
End Try

... qui ne traiterait pas MyExceptions avec différentes valeurs ErrorCode. En C # avant la v6, vous auriez à capturer et relancer l’exception MyException si 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
}
53
bzlm

Ma principale raison d'avoir un code comme:

try
{
    //Some code
}
catch (Exception e)
{
    throw;
}

est ainsi je peux avoir un point d'arrêt dans la capture, qui a un objet exception instancié. Je le fais souvent en développant/déboguant. Bien sûr, le compilateur me donne un avertissement sur tous les e inutilisés et, idéalement, ils devraient être supprimés avant la compilation.

Ils sont gentils pendant le débogage cependant.

13
user175440

Une raison valable pour renvoyer des exceptions peut être que vous souhaitez ajouter des informations à l'exception, ou peut-être inclure l'exception d'origine dans l'une de vos tâches:

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);
  }
}
11
edosoft

N'est-ce pas exactement équivalent à ne pas gérer les exceptions?

Pas exactement, ce n'est pas la même chose. Il réinitialise le stacktrace de l'exception. Bien que je convienne que c'est probablement une erreur, et donc un exemple de mauvais code.

10
Arjan Einbu

Vous ne voulez pas jeter ex - car cela perdra 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, c’est donc pire, à moins que, pour une raison quelconque, vous ne vouliez pas exposer ces informations).

8
Duncan

Un point que les gens n’ont pas mentionné est que, même si les langages .NET ne font pas vraiment la distinction, la question de savoir s’il faut passer à l’action lorsqu'une exception se produit et si on le fait résoudre ce sont des questions distinctes. Il existe de nombreux cas où il convient d'agir selon des exceptions que l'on ne peut résoudre, et dans certains cas, tout ce qui est nécessaire pour "résoudre" une exception est de dérouler la pile jusqu'à un certain point - aucune autre action n'est requise .

En raison de la sagesse commune selon laquelle il ne faut "attraper" que des choses que l'on peut "manipuler", beaucoup de code qui devrait agir lorsque des exceptions se produisent, ne le fait pas. Par exemple, beaucoup de code acquiert un verrou, met l'objet "protégé" temporairement dans un état qui viole ses invariants, le place ensuite dans un état légitime, puis libère le verrou avant que quiconque ne puisse voir l'objet. Si une exception se produit alors que l'objet se trouve dans un état dangereusement invalide, la pratique courante consiste à libérer le verrou avec l'objet toujours dans cet état. Un modèle bien meilleur consisterait à avoir une exception qui se produit alors que l'objet est dans une condition "dangereuse" invalide expressément le verrou afin que toute tentative future de l'acquérir échoue immédiatement. L'utilisation systématique d'un tel modèle améliorerait considérablement la sécurité de la gestion des exceptions dite "Pokemon", qui, à mon humble avis, a mauvaise réputation en raison principalement du code qui permet aux exceptions de disparaître sans prendre les mesures appropriées au préalable.

Dans la plupart des langages .NET, la seule façon pour le code de prendre des mesures en fonction d'une exception consiste à catch il (même s'il sait que cela ne résoudra pas l'exception), exécutez l'action en question, puis relancez throw). Une autre approche possible si le code ne tient pas compte de l'exception générée consiste à utiliser un indicateur ok avec un bloc try/finally; réglez le drapeau ok sur false avant le bloc et sur true avant la fin du bloc et avant tout return situé dans le bloc. Ensuite, dans finally, supposons que si ok ne soit pas défini, une exception doit s'être produite. Une telle approche est sémantiquement meilleure qu'un catch/throw, mais est laide et moins gérable que ce ne devrait être.

5
supercat

Bien que de nombreuses autres réponses fournissent de bons exemples de la raison pour laquelle vous pourriez vouloir capturer une exception redistribuée, personne ne semble avoir mentionné un scénario "final".

Par exemple, vous avez une méthode dans laquelle vous définissez le curseur (par exemple, un curseur d’attente), la méthode a plusieurs points de sortie (par exemple, if () return;) et vous voulez vous assurer que le curseur est réinitialisé au début. fin de la méthode.

Pour ce faire, vous pouvez envelopper tout le code dans un essai/attraper/enfin. Enfin, placez le curseur sur le curseur droit. Pour que vous n'enterriez pas d'exceptions valides, remettez-le dans la capture.

try
{
    Cursor.Current = Cursors.WaitCursor;
    // Test something
    if (testResult) return;
    // Do something else
}
catch
{
    throw;
}
finally
{
     Cursor.Current = Cursors.Default;
}
3
statler

Cela dépend de ce que vous faites dans le bloc catch et de si vous souhaitez transmettre l'erreur au code appelant ou non.

Vous pouvez dire Catch io.FileNotFoundExeption ex puis utiliser un chemin de fichier alternatif ou un nom de ce type, tout en générant l'erreur.

Faire aussi Throw au lieu de Throw Ex vous permet de conserver la trace complète de la pile. Throw ex relance la trace de pile à partir de l'instruction throw (j'espère que cela a du sens).

3
Pondidum

Cela peut être utile lorsque vos fonctions de programmation pour une bibliothèque ou une dll.

Cette structure de redistribution peut être utilisée pour réinitialiser volontairement la pile d'appels afin qu'au lieu de voir l'exception émise par une fonction individuelle à l'intérieur de la fonction, vous obtenez l'exception de la fonction elle-même.

Je pense que ceci est simplement utilisé pour que les exceptions émises soient plus propres et ne pénètrent pas dans les "racines" de la bibliothèque.

3
Jackson Tarisa

Une des raisons possibles pour attraper-jeter est de désactiver tout filtre d’exception situé plus bas dans la pile ( ancien lien aléatoire ). Mais bien sûr, si telle était l'intention, il y aurait un commentaire le disant.

3
Brian

Dans l'exemple de code que vous avez posté, il est en fait inutile de capturer l'exception car rien n'est fait sur la capture, elle est simplement rediffusée. En fait, cela fait plus de mal que de bien puisque la pile d'appels est perdue. .

Cependant, vous attraperiez une exception pour faire une logique (par exemple, la fermeture de la connexion SQL du verrou de fichier ou juste une connexion) en cas d'exception, renvoyez-la au code appelant à traiter. Cela serait plus courant dans une couche métier que le code frontal car vous voudrez peut-être que le codeur implémentant votre couche métier gère l'exception.

Pour réitérer si il n'y a pas de raison d'attraper l'exception dans l'exemple que vous avez posté. NE FAITES PAS comme ça!

2
Sheff

La plupart des réponses parlent de scénario catch-log-rethrow.

Au lieu de l'écrire dans votre code, envisagez d'utiliser AOP, en particulier Postsharp.Diagnostic.Toolkit avec OnExceptionOptions IncludeParameterValue et IncludeThisArgument

1
Michael Freidgeim

Désolé, mais de nombreux exemples de "conception améliorée" ont toujours une odeur déplorable ou peuvent être extrêmement trompeurs. Après avoir essayé {} catch {log; lancer} est tout à fait inutile. La journalisation des exceptions doit être effectuée à la place centrale de l'application. les exceptions bouillonnent quand même dans le stacktrace, pourquoi ne pas les enregistrer quelque part et près des frontières du système?

Vous devez faire preuve de prudence lorsque vous sérialisez votre contexte (par exemple, DTO dans un exemple donné) uniquement dans le message de journal. Il peut facilement contenir des informations sensibles que l’on ne souhaite peut-être pas atteindre entre les mains de toutes les personnes pouvant accéder aux fichiers journaux. Et si vous n'ajoutez aucune nouvelle information à l'exception, je ne vois vraiment pas le point d'encapsulation des exceptions. Le bon vieux Java a quelque chose à dire, il faut que l'appelant sache à quel type d'exception on devrait s'attendre lors de l'appel du code. Comme vous ne l'avez pas dans .NET, le wrapping ne sert à rien dans au moins 80% des cas que j'ai vus.

1
Joe

En plus de ce que les autres ont dit, voir ma réponse à une question connexe qui montre que la capture et le renversement ne sont pas interdits (ils sont en VB, mais une partie du code pourrait être invoquée en C # VB).

1
erikkallen