Y a-t-il une différence entre «lancer» et «lancer ex»?

437

Il y a quelques articles qui demandent quelle est déjà la différence entre ces deux.
(pourquoi dois-je même mentionner cela ...)

Mais ma question est différente dans un sens que j'appelle "throw ex" dans une autre méthode de manipulation de type erreur .

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

    private static void HandleException(Exception ex) {
        if (ex is ThreadAbortException) {
            // ignore then,
            return;
        }
        if (ex is ArgumentOutOfRangeException) { 
            // Log then,
            throw ex;
        }
        if (ex is InvalidOperationException) {
            // Show message then,
            throw ex;
        }
        // and so on.
    }
}

Si elles try & catchétaient utilisées dans le Main, alors j'utiliserais throw;pour renvoyer l'erreur. Mais dans le code simplifié ci-dessus, toutes les exceptions passent parHandleException

A throw ex;le même effet que d'appeler throwlorsqu'il est appelé à l'intérieur HandleException?

dance2die
la source
3
Il y a une différence, cela a à voir avec si ou comment la trace de pile apparaît dans l'exception, mais je ne me souviens pas laquelle est en ce moment donc je ne vais pas lister cette réponse.
Joel Coehoorn
@Joel: Merci. Je suppose que l'utilisation de l'exception HandleError est une mauvaise idée. Je voulais juste refactoriser un code de gestion des erreurs.
dance2die
1
La troisième façon consiste à encapsuler une nouvelle exception et à relancer timwise.blogspot.co.uk/2014/05/…
Tim Abell
Possibilité de doublon de différence entre lancer et lancer une nouvelle exception ()
Michael Freidgeim

Réponses:

680

Oui, il y a une différence;

  • throw exréinitialise la trace de la pile (de sorte que vos erreurs semblent provenir HandleException)
  • throw ne le fait pas - le délinquant d'origine serait conservé.

    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 resets the stack trace Coming from Method 1 and propogates it to the caller(Main)
            throw ex;
        }
    }
    
    private static void Method1()
    {
        try
        {
            throw new Exception("Inside Method1");
        }
        catch (Exception)
        {
            throw;
        }
    }
Marc Gravell
la source
28
Pour développer un peu la réponse de Marc, vous pouvez trouver plus de détails ici: geekswithblogs.net/sdorman/archive/2007/08/20/…
Scott Dorman
3
@Shaul; non, ça ne l'est pas. J'ai donné des détails dans un commentaire à votre message.
Marc Gravell
1
@Marc Gravell - mes excuses, vous aviez raison. Désolé pour le downvote; il est trop tard pour moi d'annuler ... :(
Shaul Behr
3
@Marc: Il semble que le lancer préserve le délinquant d'origine UNIQUEMENT si le lancer ne correspond pas à la méthode dans laquelle l'exception initiale a été levée (voir cette question: stackoverflow.com/questions/5152265/… )
Brann
3
@ScottDorman Il semble que votre lien ne soit pas transféré correctement après une migration de blog. On dirait qu'il vit maintenant ici . Edit: Hé, attendez, c'est votre blog! Fixez vos propres liens! ; ^ D
ruffin
96

(J'ai posté plus tôt, et @Marc Gravell m'a corrigé)

Voici une démonstration de la différence:

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

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

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

et voici la sortie:

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

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

Vous pouvez voir que dans l'exception 1, la trace de la pile revient à la DivByZero()méthode, alors que dans l'exception 2, ce n'est pas le cas.

Notez cependant que le numéro de ligne indiqué dans ThrowException1()et ThrowException2()est le numéro de ligne de l' throwinstruction, pas le numéro de ligne de l'appel à DivByZero(), ce qui est probablement logique 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 qu'il maintient le stackTrace original en mode débogage uniquement?

Shaul Behr
la source
1
C'est parce que le processus d'optimisation du compilateur insère des méthodes courtes telles que DevideByZero, donc la trace de pile EST la même. vous devriez peut-être poster ceci comme une question de son propre chef
Menahem
42

Les autres réponses sont entièrement correctes, mais cette réponse fournit des 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("Your stack trace:");
      Console.WriteLine(e.StackTrace);
      Console.WriteLine();
      if (e.InnerException == null) {
        Console.WriteLine("No inner exception.");
      } else {
        Console.WriteLine("Stack trace of your inner exception:");
        Console.WriteLine(e.InnerException.StackTrace);
      }
    }
  }

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

      //   uncomment EITHER
      //throw arithExc;
      //   OR
      //throw;
      //   OR
      //throw new Exception("We handled and wrapped your 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 throw arithExc;ligne, votre sortie est:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 44
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

Vous avez certainement perdu des informations sur le lieu de cette exception. Si vous utilisez plutôt la throw;ligne, voici ce que vous obtenez:

Handling a DivideByZeroException.
Your stack trace:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 46
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

C'est beaucoup mieux, car maintenant vous voyez que c'est la Program.Divméthode 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 du trybloc.

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

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 48
   at Program.Main() in c:\somepath\Program.cs:line 9

Stack trace of your inner exception:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 35

En particulier, vous pouvez voir que c'est la ligne 35 qui pose problème. Cependant, cela nécessite que les gens recherchent le InnerException, et il semble quelque peu indirect d'utiliser des exceptions internes dans des cas simples.

Dans ce billet de blog, ils conservent le numéro de ligne (ligne du bloc try) en appelant (par réflexion) la internalméthode intance InternalPreserveStackTrace()sur l' Exceptionobjet. Mais ce n'est pas agréable d'utiliser une réflexion comme celle-ci (le .NET Framework pourrait changer leurs internalmembres un jour sans avertissement).

Jeppe Stig Nielsen
la source
6

comprenons la différence entre lancer et lancer ex. J'ai entendu dire que dans de nombreuses interviews .net, cette question courante était posée.

Juste pour donner un aperçu de ces deux termes, throw et throw ex sont tous deux utilisés pour comprendre où l'exception s'est produite. Throw ex réécrit la trace d'exception de la pile quel que soit l'endroit où elle a été levée.

Comprenons avec un exemple.

Comprenons d'abord Throw.

static void Main(string[] args) {
    try {
        M1();
    } catch (Exception ex) {
        Console.WriteLine(" -----------------Stack Trace Hierarchy -----------------");
        Console.WriteLine(ex.StackTrace.ToString());
        Console.WriteLine(" ---------------- Method Name / Target Site -------------- ");
        Console.WriteLine(ex.TargetSite.ToString());
    }
    Console.ReadKey();
}

static void M1() {
    try {
        M2();
    } catch (Exception ex) {
        throw;
    };
}

static void M2() {
    throw new DivideByZeroException();
}

la sortie de ce qui précède est ci-dessous.

montre la hiérarchie complète et le nom de la méthode où l'exception a été levée. C'est M2 -> M2. avec les numéros de ligne

entrez la description de l'image ici

Deuxièmement .. permet de comprendre par jet ex. Remplacez simplement throw par throw ex dans le bloc catch de la méthode M2. comme ci-dessous.

entrez la description de l'image ici

la sortie du code throw ex est comme ci-dessous.

entrez la description de l'image ici

Vous pouvez voir la différence dans la sortie. Throw ex ignore simplement toute la hiérarchie précédente et réinitialise la trace de la pile avec la ligne / méthode où throw ex est écrit.

Mahesh
la source
5

Lorsque vous le faites throw ex, cette exception levée devient celle "d'origine". Ainsi, toute trace de pile précédente ne sera pas là.

Si vous le faites throw, l'exception va juste au bout de la ligne et vous obtiendrez la trace complète de la pile.

GR7
la source
4

Non, cela entraînera l'exception pour avoir une trace de pile différente. Seule l'utilisation d'un throwobjet sans exception dans le catchgestionnaire laissera la trace de pile inchangée.

Vous souhaiterez peut-être renvoyer un booléen à partir de HandleException, que l'exception soit renvoyée ou non.

Lucero
la source
4

MSDN signifie :

Une fois qu'une exception est levée, une partie des informations qu'elle contient est la trace de la pile. La trace de pile est une liste de la hiérarchie des appels de méthode qui commence par la méthode qui lève l'exception et se termine par la méthode qui intercepte l'exception. Si une exception est renvoyée en spécifiant l'exception dans l'instruction throw, la trace de pile est redémarrée à la méthode actuelle et la liste des appels de méthode entre la méthode d'origine qui a levé l'exception et la méthode actuelle est perdue. Pour conserver les informations de trace de pile d'origine avec l'exception, utilisez l'instruction throw sans spécifier l'exception.

COMME
la source
2

Regardez ici: http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html

Lancer :

try 
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw;
}

Il conserve les informations de la pile avec exception

Cela s'appelle "Rethrow"

Si vous voulez lever une nouvelle exception,

throw new ApplicationException("operation failed!");

Lancer Ex :

try
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw ex;
}

Il n'enverra pas d'informations sur la pile avec exception

Cela s'appelle "Briser la pile"

Si vous voulez lever une nouvelle exception,

throw new ApplicationException("operation failed!",ex);
Aaaaaaaa
la source
0

Pour vous donner une perspective différente à ce sujet, l'utilisation de throw est particulièrement utile si vous fournissez une API à un client et que vous souhaitez fournir des informations de trace de pile détaillées pour votre bibliothèque interne. En utilisant throw ici, j'obtiendrais la trace de la pile dans ce cas de la bibliothèque System.IO.File pour File.Delete. Si j'utilise throw ex, ces informations ne seront pas transmises à mon gestionnaire.

static void Main(string[] args) {            
   Method1();            
}

static void Method1() {
    try {
        Method2();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method1");             
    }
}

static void Method2() {
    try {
        Method3();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method2");
        Console.WriteLine(ex.TargetSite);
        Console.WriteLine(ex.StackTrace);
        Console.WriteLine(ex.GetType().ToString());
    }
}

static void Method3() {
    Method4();
}

static void Method4() {
    try {
        System.IO.File.Delete("");
    } catch (Exception ex) {
        // Displays entire stack trace into the .NET 
        // or custom library to Method2() where exception handled
        // If you want to be able to get the most verbose stack trace
        // into the internals of the library you're calling
        throw;                
        // throw ex;
        // Display the stack trace from Method4() to Method2() where exception handled
    }
}
Charles Owen
la source
-1
int a = 0;
try {
    int x = 4;
    int y ;
    try {
        y = x / a;
    } catch (Exception e) {
        Console.WriteLine("inner ex");
        //throw;   // Line 1
        //throw e;   // Line 2
        //throw new Exception("devide by 0");  // Line 3
    }
} catch (Exception ex) {
    Console.WriteLine(ex);
    throw ex;
}
  1. si toutes les lignes 1, 2 et 3 sont commentées - Sortie - ex interne

  2. si toutes les lignes 2 et 3 sont commentées - Sortie - ex interne System.DevideByZeroException: {"Tentative de division par zéro."} ---------

  3. si toutes les lignes 1 et 2 sont commentées - Sortie - ex interne System.Exception: diviser par 0 ----

  4. si toutes les lignes 1 et 3 sont commentées - Sortie - ex interne System.DevideByZeroException: {"Tentative de division par zéro."} ---------

et StackTrace sera réinitialisé en cas de lancer ex;

Bhanu pratap
la source