web-dev-qa-db-fra.com

Quelle est la bonne façon de renvoyer une exception en C #?

J'ai une question pour vous qui vient du fait que mon partenaire fait les choses différemment des miennes.

Est-il préférable de faire ceci:

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

ou ca:

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

Est-ce qu'ils font la même chose? Est-ce que l'un est meilleur que l'autre?

427

Vous devriez toujours utiliser la syntaxe suivante pour renvoyer une exception, sinon vous falsifiez la trace de la pile:

throw;

Si vous imprimez la trace résultant de "throw ex", vous verrez qu'elle se termine sur cette instruction et non sur la source réelle de l'exception.

Fondamentalement, utiliser "lancer ex" devrait être considéré comme une infraction pénale.

749

Mes préférences sont d'utiliser

try 
{
}
catch (Exception ex)
{
     ...
     throw new Exception ("Put more context here", ex)
}

Cela préserve l'erreur d'origine, mais vous permet de mettre plus de contexte, tel qu'un ID d'objet, une chaîne de connexion, des choses comme ça. Souvent, mon outil de génération de rapports sur les exceptions aura 5 exceptions chaînées à signaler, chacune rapportant plus de détails.

152
RB.

Si vous lancez une exception avec out une variable (le deuxième exemple), StackTrace inclura la méthode d'origine qui a levé l'exception.

Dans le premier exemple, StackTrace sera modifié pour refléter la méthode actuelle.

Exemple:

static string ReadAFile(string fileName) {
    string result = string.Empty;
    try {
        result = File.ReadAllLines(fileName);
    } catch(Exception ex) {
        throw ex; // This will show ReadAFile in the StackTrace
        throw;    // This will show ReadAllLines in the StackTrace
    }
36
Bobby Z

Le premier conserve la trace de pile d'origine de l'exception, le second la remplace par l'emplacement actuel.

Par conséquent, le premier est de loin le mieux.

22
Quibblesome

Je sais que c'est une vieille question, mais je vais y répondre car je ne suis pas d'accord avec toutes les réponses ici.

Maintenant, je conviens que la plupart du temps, soit vous voulez faire une simple throw, pour conserver le plus d'informations possible sur ce qui ne va pas, ou vous voulez lancer une nouvelle exception pouvant contenir cette exception. exception interne, ou non, en fonction de la probabilité que vous souhaitiez connaître les événements internes qui l'ont provoquée.

Il y a une exception cependant. Il existe plusieurs cas dans lesquels une méthode appelle une autre méthode et une condition qui provoque une exception dans l'appel interne doit être considérée comme la même exception lors de l'appel externe.

Un exemple est une collection spécialisée mise en œuvre en utilisant une autre collection. Disons que c'est un DistinctList<T> qui enveloppe un List<T> mais refuse les éléments en double.

Si quelqu'un appelle ICollection<T>.CopyTo sur votre classe de collection, il peut s'agir simplement d'un appel direct à CopyTo sur la collection interne (si c'est le cas, toute la logique personnalisée appliquée uniquement à l'ajout ou à la configuration de la collection ). Désormais, les conditions dans lesquelles cet appel serait lancé sont exactement les mêmes que celles dans lesquelles votre collection devrait correspondre pour correspondre à la documentation de ICollection<T>.CopyTo.

Maintenant, vous pouvez simplement ne pas attraper l’exécution et la laisser passer. Ici cependant, l'utilisateur obtient une exception de List<T> lorsqu'il appelait quelque chose sur un DistinctList<T>. Pas la fin du monde, mais vous voudrez peut-être cacher ces détails d'implémentation.

Ou vous pouvez faire votre propre vérification:

public CopyTo(T[] array, int arrayIndex)
{
  if(array == null)
    throw new ArgumentNullException("array");
  if(arrayIndex < 0)
    throw new ArgumentOutOfRangeException("arrayIndex", "Array Index must be zero or greater.");
  if(Count > array.Length + arrayIndex)
    throw new ArgumentException("Not enough room in array to copy elements starting at index given.");
  _innerList.CopyTo(array, arrayIndex);
}

Ce n'est pas le pire code car il est standard et nous pouvons probablement le copier à partir d'une autre implémentation de CopyTo alors que ce n'était pas une simple transmission et que nous devions le mettre en œuvre nous-mêmes. Néanmoins, il répète inutilement les vérifications exactes mêmes qui seront effectuées dans _innerList.CopyTo(array, arrayIndex), de sorte que la seule chose ajoutée à notre code est 6 lignes où il pourrait y avoir un bogue.

Nous pourrions vérifier et emballer:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentNullException ane)
  {
    throw new ArgumentNullException("array", ane);
  }
  catch(ArgumentOutOfRangeException aore)
  {
    throw new ArgumentOutOfRangeException("Array Index must be zero or greater.", aore);
  }
  catch(ArgumentException ae)
  {
    throw new ArgumentException("Not enough room in array to copy elements starting at index given.", ae);
  }
}

En ce qui concerne le nouveau code ajouté qui pourrait potentiellement être bogué, c'est encore pire. Et nous ne gagnons rien des exceptions internes. Si nous passons un tableau null à cette méthode et recevons un ArgumentNullException, nous n'apprendrons rien en examinant l'exception interne et en apprenant qu'un appel à _innerList.CopyTo a été passé un tableau nul et a jeté un ArgumentNullException.

Ici, nous pouvons faire tout ce que nous voulons avec:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentException ae)
  {
    throw ae;
  }
}

Chacune des exceptions que nous nous attendons à devoir lancer si l'utilisateur l'appelle avec des arguments incorrects sera correctement renvoyée par cette relance. S'il y a un bug dans la logique utilisée ici, c'est dans l'une des deux lignes - soit nous avons eu tort de décider que c'était un cas où cette approche fonctionnait, soit nous nous sommes trompés en ayant ArgumentException comme type d'exception recherché. Ce sont les deux seuls bugs que le bloc catch peut avoir.

Maintenant. Je suis toujours d’accord que la plupart du temps, soit vous voulez un throw; simple, soit vous voulez construire votre propre exception pour que le problème corresponde plus directement du point de vue de la méthode en question. Il y a des cas comme celui-ci où une nouvelle projection comme celle-ci est plus logique et il y en a beaucoup d'autres. Par exemple. Pour prendre un exemple très différent, si un lecteur de fichier ATOM implémenté avec un FileStream et un XmlTextReader reçoit une erreur de fichier ou un XML invalide, il voudra peut-être jeter exactement le même exception reçue de ces classes, mais l'appelant doit se dire que c'est AtomFileReader qui jette un FileNotFoundException ou XmlException, de sorte qu'il pourrait être candidat à un nouveau lancement.

Modifier:

Nous pouvons également combiner les deux:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentException ae)
  {
    throw ae;
  }
  catch(Exception ex)
  {
    //we weren't expecting this, there must be a bug in our code that put
    //us into an invalid state, and subsequently let this exception happen.
    LogException(ex);
    throw;
  }
}
17
Jon Hanna

Vous devriez toujours utiliser "jeter;" renvoyer les exceptions dans .NET,

Reportez-vous, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

Fondamentalement, MSIL (CIL) a deux instructions - "throw" et "rethrow" et C #, "throw ex;" est compilé dans le "throw" de MSIL et le "throw;" de C # - dans MSIL "renverser"! Fondamentalement, je peux voir la raison pour laquelle "throw ex" remplace la trace de pile.

8
Vinod T. Patil

Le premier est meilleur. Si vous essayez de déboguer le second et de regarder la pile d'appels, vous ne verrez pas d'où vient l'origine de l'exception. Il y a des astuces pour garder la pile d'appels intacte (essayez de chercher, elle a déjà été répondue) si vous avez vraiment besoin de la renvoyer.

5
Mendelt

Ça dépend. Dans une version de débogage, je souhaite voir la trace de pile d'origine avec le moins d'effort possible. Dans ce cas, "jeter;" correspond à la facture. Dans une version validée, cependant, (a) je souhaite consigner l'erreur avec la trace de pile d'origine incluse et, une fois que cela est fait, (b) remodelez la gestion des erreurs pour qu'elle ait plus de sens pour l'utilisateur. Ici, "Exception de projection" a du sens. Il est vrai que retransmettre l'erreur supprime la trace de pile d'origine, mais un non-développeur n'obtient rien en voyant les informations de trace de pile, il est donc correct de retransmettre l'erreur.

        void TrySuspectMethod()
        {
            try
            {
                SuspectMethod();
            }
#if DEBUG
            catch
            {
                //Don't log error, let developer see 
                //original stack trace easily
                throw;
#else
            catch (Exception ex)
            {
                //Log error for developers and then 
                //throw a error with a user-oriented message
                throw new Exception(String.Format
                    ("Dear user, sorry but: {0}", ex.Message));
#endif
            }
        }

La façon dont la question est formulée, opposant "Throw:" à "Throw ex;" en fait un peu un hareng. Le vrai choix est entre "Throw;" et "Throw Exception", où "Throw ex;" est un cas spécial peu probable de "Throw Exception".

3
Perry Tribolet

J'ai constaté que si l'exception est renvoyée dans la même méthode que celle qu'elle a capturée, la trace de pile n'est pas conservée, pour ce qu'elle vaut.

void testExceptionHandling()
{
    try
    {
        throw new ArithmeticException("illegal expression");
    }
    catch (Exception ex)
    {
        throw;
    }
    finally
    {
        System.Diagnostics.Debug.WriteLine("finally called.");
    }
}
3
James