559 votes

Y a-t-il une différence entre "throw" et "throw ex"?

Il y a déjà des publications qui demandent quelle est la différence entre les deux.
(pourquoi dois-je même mentionner ça...)

Mais ma question est différente dans le sens où j'appelle "throw ex" dans une autre méthode de gestion d'erreur digne d'un dieu.

public class Program {
    public static void Main(string[] args) {
        try {
            // quelque chose
        } catch (Exception ex) {
            HandleException(ex);
        }
    }

    private static void HandleException(Exception ex) {
        if (ex is ThreadAbortException) {
            // ignorer alors,
            return;
        }
        if (ex is ArgumentOutOfRangeException) { 
            // Enregistrer alors,
            throw ex;
        }
        if (ex is InvalidOperationException) {
            // Afficher un message alors,
            throw ex;
        }
        // et ainsi de suite.
    }
}

Si try & catch étaient utilisés dans le Main, alors j'utiliserais throw; pour relancer l'erreur. Mais dans le code simplifié ci-dessus, toutes les exceptions passent par HandleException

Est-ce que throw ex; a le même effet que d'appeler throw lorsqu'il est appelé à l'intérieur de HandleException?

5 votes

Il y a une différence, elle concerne la manière dont la trace de la pile apparaît dans l'exception, mais je ne me souviens pas laquelle est laquelle pour l'instant, donc je ne vais pas la lister comme réponse.

0 votes

@Joel: Merci. Je suppose que l'utilisation de l'exception HandleError est une mauvaise idée. Je voulais juste refactoriser du code de gestion des erreurs.

1 votes

La troisième façon est d'emballer dans une nouvelle exception et de la relancer timwise.blogspot.co.uk/2014/05/…

840voto

Marc Gravell Points 482669

Oui, il y a une différence.

  • throw ex réinitialise la trace de la pile (donc vos erreurs sembleraient provenir de HandleException)

  • throw ne le fait pas - le coupable d'origine serait préservé.

     static void Main(string[] args)
     {
         try
         {
             Method2();
         }
         catch (Exception ex)
         {
             Console.Write(ex.StackTrace.ToString());
             Console.ReadKey();
         }
     }
    
     private static void Method2()
     {
         try
         {
             Method1();
         }
         catch (Exception ex)
         {
             //throw ex réinitialise la trace de la pile venant de la méthode 1 et la propage à l'appelant (Main)
             throw ex;
         }
     }
    
     private static void Method1()
     {
         try
         {
             throw new Exception("À l'intérieur de la méthode 1");
         }
         catch (Exception)
         {
             throw;
         }
     }

0 votes

@Scott: Merci pour le lien. Et j'ai également découvert comment extraire le gestionnaire d'erreurs dans la question suivante: stackoverflow.com/questions/730300/…

6 votes

@Marc : Il semble que throw préserve le délinquant original UNIQUEMENT si le throw n'est pas dans la méthode dans laquelle l'exception initiale a été lancée (voir cette question : stackoverflow.com/questions/5152265/…).

0 votes

@Xenan - tu n'es pas le seul - je le confonds toujours aussi. Cela semble juste étrange.

117voto

Shaul Points 8267

(J'ai posté précédemment, et @Marc Gravell m'a corrigé)

Voici une démonstration de la différence :

static void Main(string[] args) {
    try {
        ThrowException1(); // ligne 19
    } catch (Exception x) {
        Console.WriteLine("Exception 1:");
        Console.WriteLine(x.StackTrace);
    }
    try {
        ThrowException2(); // ligne 25
    } catch (Exception x) {
        Console.WriteLine("Exception 2:");
        Console.WriteLine(x.StackTrace);
    }
}

private static void ThrowException1() {
    try {
        DivByZero(); // ligne 34
    } catch {
        throw; // ligne 36
    }
}
private static void ThrowException2() {
    try {
        DivByZero(); // ligne 41
    } catch (Exception ex) {
        throw ex; // ligne 43
    }
}

private static void DivByZero() {
    int x = 0;
    int y = 1 / x; // ligne 49
}

et voici la sortie :

Exception 1:
   at UnitTester.Program.DivByZero() in \Dev\UnitTester\Program.cs:line 49
   at UnitTester.Program.ThrowException1() in \Dev\UnitTester\Program.cs:line 36
   at UnitTester.Program.TestExceptions() in \Dev\UnitTester\Program.cs:line 19

Exception 2:
   at UnitTester.Program.ThrowException2() in \Dev\UnitTester\Program.cs:line 43
   at UnitTester.Program.TestExceptions() in \Dev\UnitTester\Program.cs:line 25

Vous pouvez voir qu'en Exception 1, la trace de la pile remonte à la méthode DivByZero(), tandis qu'en Exception 2, ce n'est pas le cas.

Notez cependant que le numéro de ligne affiché dans ThrowException1() et ThrowException2() est le numéro de ligne de l'instruction throw, pas le numéro de ligne de l'appel à DivByZero(), ce qui a probablement du sens maintenant que j'y pense un peu...

Sortie en mode Release

Exception 1:

at ConsoleAppBasics.Program.ThrowException1()
at ConsoleAppBasics.Program.Main(String[] args)

Exception 2:

at ConsoleAppBasics.Program.ThrowException2()
at ConsoleAppBasics.Program.Main(String[] args)

Est-ce que cela maintient la trace de pile d'origine en mode debug seulement ?

3 votes

C'est parce que le processus d'optimisation du compilateur intègre des méthodes courtes telles que DevideByZero, donc la trace de la pile EST la même. peut-être devriez-vous poster ceci comme une question à part

66voto

Shivprasad Koirala Points 1327

Lancer préserve la trace de la pile. Donc, disons que Source1 lance Error1, il est attrapé par Source2 et que Source2 dit lancer alors l'erreur Source1 + l'erreur Source2 seront disponibles dans la trace de la pile.

Lancer ex ne préserve pas la trace de la pile. Donc, toutes les erreurs de Source1 seront effacées et seule l'erreur Source2 sera envoyée au client.

Parfois, juste lire les choses n'est pas clair, je suggérerais de regarder cette démo vidéo pour plus de clarté, Lancer vs Lancer ex en C#.

Lancer vs Lancer ex

47voto

Jeppe Stig Nielsen Points 17887

Les autres réponses sont tout à fait correctes, mais cette réponse fournit quelques détails supplémentaires, je pense.

Considérez cet exemple:

using System;

static class Program {
  static void Main() {
    try {
      ThrowTest();
    } catch (Exception e) {
      Console.WriteLine("Votre trace de pile:");
      Console.WriteLine(e.StackTrace);
      Console.WriteLine();
      if (e.InnerException == null) {
        Console.WriteLine("Pas d'exception interne.");
      } else {
        Console.WriteLine("Trace de pile de votre exception interne:");
        Console.WriteLine(e.InnerException.StackTrace);
      }
    }
  }

  static void ThrowTest() {
    decimal a = 1m;
    decimal b = 0m;
    try {
      Mult(a, b);  // ligne 34
      Div(a, b);   // ligne 35
      Mult(b, a);  // ligne 36
      Div(b, a);   // ligne 37
    } catch (ArithmeticException arithExc) {
      Console.WriteLine("Gestion d'une {0}.", arithExc.GetType().Name);

      //   décommenter L'UNE OU L'AUTRE
      //throw arithExc;
      //   OU
      //throw;
      //   OU
      //throw new Exception("Nous avons géré et enveloppé votre exception", arithExc);
    }
  }

  static void Mult(decimal x, decimal y) {
    decimal.Multiply(x, y);
  }
  static void Div(decimal x, decimal y) {
    decimal.Divide(x, y);
  }
}

Si vous décommentez la ligne throw arithExc;, votre sortie est:

Gestion d'une DivideByZeroException.
Votre trace de pile:
   at Program.ThrowTest() dans c:\somepath\Program.cs:ligne 44
   at Program.Main() dans c:\somepath\Program.cs:ligne 9

Pas d'exception interne.

Vous avez sûrement perdu des informations sur l'endroit où cette exception s'est produite. Si au lieu de cela vous utilisez la ligne throw;, voici ce que vous obtenez:

Gestion d'une DivideByZeroException.
Votre trace de pile:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) dans c:\somepath\Program.cs:ligne 58
   at Program.ThrowTest() dans c:\somepath\Program.cs:ligne 46
   at Program.Main() dans c:\somepath\Program.cs:ligne 9

Pas d'exception interne.

C'est beaucoup mieux, car maintenant vous voyez que c'était la méthode Program.Div qui vous a posé problème. Mais il est toujours difficile de voir si ce problème vient de la ligne 35 ou de la ligne 37 dans le bloc try.

Si vous utilisez la troisième alternative, envelopper dans une exception externe, vous ne perdez aucune information:

Gestion d'une DivideByZeroException.
Votre trace de pile:
   at Program.ThrowTest() dans c:\somepath\Program.cs:ligne 48
   at Program.Main() dans c:\somepath\Program.cs:ligne 9

Trace de pile de votre exception interne:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) dans c:\somepath\Program.cs:ligne 58
   at Program.ThrowTest() dans c:\somepath\Program.cs:ligne 35

En particulier, vous pouvez voir que c'est la ligne 35 qui provoque le problème. Cependant, cela oblige les gens à rechercher l'InnerException, et cela semble quelque peu indirect d'utiliser des exceptions internes dans des cas simples.

Dans cet article de blog, ils conservent le numéro de ligne (ligne du bloc try) en appelant (via réflexion) la méthode interne InternalPreserveStackTrace() sur l'objet Exception. Mais il n'est pas recommandé d'utiliser la réflexion de cette manière (le .NET Framework pourrait modifier ses membres internes un jour sans avertissement).

11voto

GR7 Points 1330

Lorsque vous faites throw ex, cette exception lancée devient celle "d'origine". Ainsi, toute la pile d'exécution précédente ne sera pas présente.

Si vous utilisez throw, l'exception continuera simplement le long de la ligne et vous obtiendrez la pile d'exécution complète.

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