684 votes

Pourquoi attraper et relancer 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 lance une WtfException... N'est-ce pas exactement l'équivalent de ne pas gérer du tout 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 est-ce que je rate quelque chose de fondamental dans la gestion des erreurs en C# ? C'est à peu près la même chose qu'en Java (sauf les exceptions vérifiées), n'est-ce pas ? ... C'est-à-dire qu'ils ont tous deux raffiné le C++.

La question de Stack Overflow La différence entre relancer une capture sans paramètre et ne rien faire ? semble confirmer ma thèse selon laquelle le "try-catch-throw" est une erreur.


EDIT :

Juste pour résumer pour ceux qui trouveront 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 la trace de la pile peuvent être cruciales pour identifier la cause profonde 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 les plus spécifiques avant les moins spécifiques (tout comme Java).


Références :

21 votes

Bon résumé ; points supplémentaires pour avoir inclus le bloc final.

0 votes

Je voudrais ajouter que vous pouvez utiliser le "throw ;" pour être encore plus utile en ajoutant les paramètres qui ont été envoyés à la méthode dans la collection e.Data avant l'instruction "throw ;".

0 votes

@MickTheWarMachineDesigner (et peintre à temps partiel). Hein ? Vous parlez de la gestion des exceptions de Microshite Suckwell (probablement à partir de 2005, pour ce que j'en sais). Je parlais de la gestion des exceptions en général. Et oui, j'ai appris des choses depuis que j'ai posté ceci il y a presque quatre ans..... Mais oui, j'avoue que vous avez un point valable, mais je pense que vous avez manqué le vrai point ; si vous voyez ce que je veux dire ? Cette question porte sur la gestion généralisée des exceptions en C#, et plus particulièrement sur le rejet des exceptions... de TOUTES sortes. C'est cool ?

510voto

Fredrik Mörk Points 85694

Tout d'abord, la façon dont le code de l'article le fait est mauvaise. throw ex réinitialisera la pile d'appels dans l'exception au point où se trouve l'instruction throw, perdant ainsi l'information sur l'endroit où l'exception a été créée.

Deuxièmement, si vous vous contentez d'attraper et de relancer comme ça, je ne vois pas de valeur ajoutée, l'exemple de code ci-dessus serait tout aussi bien (ou, étant donné le throw ex encore mieux) sans le try-catch.

Cependant, il existe des cas où vous pouvez vouloir attraper et relancer une exception. La journalisation pourrait être l'un de ces cas :

try 
{
    // code that may throw exceptions    
}
catch(Exception ex) 
{
    // add error logging here
    throw;
}

7 votes

@Fredrick, juste pour info (bien que vous le sachiez probablement) si vous ne comptez pas utiliser ce système ex il n'est pas nécessaire de l'instancier.

4 votes

@Eoin : Je suis conscient de cela. Dans ce cas, j'ai suggéré d'attraper l'exception afin de faire quelque chose (comme la journalisation) avec elle avant de la relancer, et j'aurai alors besoin de l'instance d'exception. Mais bon point.

91 votes

@Eoin : Si elle n'est pas instanciée, il serait plutôt difficile de l'enregistrer.

136voto

Eoin Campbell Points 22861

Ne faites pas ça,

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

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

L'un ou l'autre,

try { ... }
catch { throw; }

OU

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

L'une des raisons pour lesquelles vous pourriez vouloir relancer est que vous traitez différentes exceptions, par exemple 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
}

8 votes

Pourquoi ne pas simplement laisser catch { throw } de côté ?

1 votes

J'ai ajouté un exemple pour expliquer pourquoi, mais oui, dans ce cas artificiel, vous pourriez le laisser de côté.

3 votes

Il est toujours utile de laisser catch {throw ; } au bas d'une liste de catch de type d'exception spécifique, car cela prouve que l'auteur a considéré le cas, bien qu'un commentaire puisse également suffire. Ne pas deviner quand on lit du code est une bonne chose.

62voto

bzlm Points 5500

C# (avant C# 6) ne prend pas en charge les "exceptions filtrées" CIL, contrairement à VB. Ainsi, en C# 1-5, l'une des raisons de relancer une exception est que l'on ne dispose pas de suffisamment d'informations au moment de catch() pour déterminer si l'on voulait 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 pouvait pas gérer les MyExceptions avec des valeurs ErrorCode différentes. Dans le C# antérieur à la version 6, il fallait attraper et relancer l'exception MyException si le ErrorCode n'était pas 123 :

try 
{
   ...
}
catch(MyException ex)
{
    if (ex.ErrorCode != 123) throw;
    ...
}

Depuis C# 6.0, vous pouvez filtrer tout comme avec VB :

try 
{
  // Do stuff
} 
catch (Exception e) when (e.ErrorCode == 123456) // filter
{
  // Handle, other exceptions will be left alone and bubble up
}

2 votes

Dave, mais (en java au moins) tu ne lancerais pas une MyException "générique", tu définirais un type d'exception SPECIFIQUE et tu le lancerais, ce qui permettrait de le différencier par type dans le bloc catch... Mais oui, si vous n'êtes pas l'architecte de l'exception (je pense ici à l'exception SQLException de JDBC (Java encore), qui est dégoûtante et générique, et expose la méthode getErrorCode()... Hmmm... Vous avez un point, c'est juste que je pense qu'il y a une meilleure façon de le faire, lorsque cela est possible. Cheers Mate. J'apprécie beaucoup ton temps. Keith.

1 votes

Eh bien, la question est "Pourquoi attraper et relancer une exception en C# ?", et voici une réponse. =] ...et même avec des exceptions spécialisées, les filtres d'exception ont un sens : considérez le cas où vous gérez, disons, une SqlTimeoutException et une SqlConnectionResetException, qui sont toutes deux des SqlException. Les filtres d'exception vous permettent d'attraper une SqlException uniquement lorsqu'il s'agit de l'une de ces deux exceptions, donc au lieu d'encombrer votre try/catch avec une gestion identique pour ces deux exceptions, vous pourriez "catch SqlException ex when ex is SqlTimeoutException Or ex is SqlConnectionResetException". (Je ne suis pas Dave btw)

3 votes

Les exceptions filtrées arrivent en C# 6 !

13voto

edosoft Points 7783

Une raison valable pour relancer des exceptions peut être que vous voulez ajouter des informations à l'exception, ou peut-être envelopper l'exception originale dans une exception de votre propre création :

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

0 votes

Merci, oui, l'encapsulation des exceptions (surtout en chaîne) est parfaitement saine... ce qui n'est pas sain, c'est d'attraper une exception juste pour pouvoir jeter la trace de la pile, ou pire, la manger.

10voto

Arjan Einbu Points 7941

N'est-ce pas exactement l'équivalent de ne pas pas du tout les exceptions ?

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X