Question

Je réémettre une exception à "jeter;", mais le stacktrace est incorrect:

static void Main(string[] args) {
    try {
        try {
            throw new Exception("Test"); //Line 12
        }
        catch (Exception ex) {
            throw; //Line 15
        }
    }
    catch (Exception ex) {
        System.Diagnostics.Debug.Write(ex.ToString());
    }
    Console.ReadKey();
}

Le droit stacktrace devrait être:

System.Exception: Test
   at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 12

Mais je reçois:

System.Exception: Test
   at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 15

Mais la ligne 15 est la position du « jet; ». Je l'ai testé avec .NET 3.5.

Était-ce utile?

La solution

Lancer deux fois dans la même méthode est probablement un cas particulier - je n'ai pas été en mesure de créer une trace de la pile où des lignes différentes dans la même méthode suivent. Comme le mot l'indique, un « montre de trace de pile » vous les cadres de pile qu'une exception parcourues. Et il n'y a qu'un seul cadre de pile par appel de méthode!

Si vous jetez d'une autre méthode, throw; ne supprime pas l'entrée pour Foo(), comme prévu:

  static void Main(string[] args)
  {
     try
     {
        Rethrower();
     }
     catch (Exception ex)
     {
        Console.Write(ex.ToString());
     }
     Console.ReadKey();
  }

  static void Rethrower()
  {
     try
     {
        Foo();
     }
     catch (Exception ex)
     {
        throw;
     }

  }

  static void Foo()
  {
     throw new Exception("Test"); 
  }

Si vous modifiez Rethrower() et remplacez throw; par throw ex;, l'entrée Foo() dans la trace de la pile disparaît. Encore une fois, c'est le comportement attendu.

Autres conseils

Il est quelque chose qui peut être considéré comme prévu. La modification trace de la pile est d'habitude le cas si vous spécifiez throw ex;, FxCop sera de vous informer que la pile est modifiée. Si vous faites throw;, aucun avertissement est généré, mais encore, la trace sera modifiée. Donc, malheureusement pour l'instant il est le meilleur de ne pas attraper l'ex ou de le jeter comme un intérieur. Je pense qu'il devrait être considéré comme un Impact de Windows ou lissée comme - édité . Jeff Richter décrit cette situation plus en détail dans son "CLR via C #" :

  

Le code suivant lance le même   objet d'exception qu'il a attrapé et   provoque le CLR pour réinitialiser son départ   le point de l'exception:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw e; // CLR thinks this is where exception originated.
    // FxCop reports this as an error
  }
}
     

En revanche, si vous re-jeter un   objet d'exception en utilisant le jet   mot-clé par lui-même, le CLR ne   réinitialiser le point de départ de la pile. le   code suivant re-lance même   objet d'exception qu'il a attrapé,   l'origine du CLR pour ne pas réinitialiser son   point de départ de l'exception:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw; // This has no effect on where the CLR thinks the exception
    // originated. FxCop does NOT report this as an error
  }
}
     

En fait, la seule différence entre   ces deux fragments de code est ce que le   CLR pense est l'emplacement d'origine   où l'exception a été levée.    Malheureusement, lorsque vous lancer ou   réémettre une exception, Windows ne   réinitialiser le point de départ de la pile. Alors   si l'exception devient non prise en charge,   l'emplacement de la pile qui obtient rapporté   Windows Error Reporting est le   emplacement du dernier jet ou   re-jet, même si le CLR sait   l'emplacement de la pile lorsque l'original   exception a été levée. C'est   malheureux, car il rend le débogage   les applications qui ont échoué dans le   domaine beaucoup plus difficile. Certains   les développeurs ont trouvé cette façon   intolérable qu'ils ont choisi un   autre façon de mettre en œuvre leur code   faire en sorte que la pile de trace vraiment   reflète l'endroit où un   exception a été levée:

private void SomeMethod() {
  Boolean trySucceeds = false;
  try {
    ...
    trySucceeds = true;
  }
  finally {
    if (!trySucceeds) { /* catch code goes in here */ }
  }
}

Ceci est une limitation bien connue dans la version Windows du CLR. Il utilise Windows' un support intégré pour la gestion des exceptions (SEH). Le problème est, il est bâti à base de pile et un procédé ne comporte qu'un seul cadre de pile. Vous pouvez facilement résoudre le problème en déplaçant le bloc try / catch interne dans une autre méthode d'aide, créant ainsi un autre cadre de pile. Une autre conséquence de cette limitation est que le compilateur JIT ne sera pas en ligne toute méthode qui contient une instruction try.

  

Comment puis-je conserver le stacktrace REAL?

Vous lancez une nouvelle exception, et inclut l'exception d'origine comme l'exception interne.

  

mais est moche ... long ... fait votre choix à l'exception de rigth jeter ....

Vous avez tort sur les laid mais juste sur les deux autres points. La règle de base est la suivante: ne se coincent pas à moins que vous allez faire quelque chose avec elle, comme une pellicule, le modifier, l'avaler, ou l'enregistrer. Si vous décidez de catch puis throw à nouveau, assurez-vous que vous faites quelque chose avec elle, sinon tout simplement le laisser bouillonner.

Vous pouvez également être tenté de mettre une prise simplement pour que vous puissiez d'arrêt au sein des prises, mais le débogueur Visual Studio a suffisamment d'options pour rendre cette pratique inutile, essayez d'utiliser les exceptions de première chance ou points d'arrêt conditionnels à la place.

Modifier / Remplacer

Le comportement est en fait différent, mais si subtilement. Pour ce qui est pourquoi le comportement si différent, je devrai remettre à un expert CLR.

EDIT: de réponse AlexD semble indiquer que c'est par la conception.

Lancer l'exception dans la même méthode que les captures, il confond la situation un peu, nous allons jeter une exception à une autre méthode:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Throw();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    public static void Throw()
    {
        int a = 0;
        int b = 10 / a;
    }
}

Si throw; est utilisé, le callstack est (les numéros de ligne remplacés par code):

at Throw():line (int b = 10 / a;)
at Main():line (throw;) // This has been modified

Si throw ex; est utilisé, le callstack est:

at Main():line (throw ex;)

Si exception n'est pas pris, le callstack est:

at Throw():line (int b = 10 / a;)
at Main():line (Throw())

Testé dans .NET 4 / VS 2010

Il y a une double question .

Si je comprends bien - deux pas; est compilé dans « rethrow » instruction MSIL et modifie la dernière image de la stack-trace.

Je pense qu'elle doit garder la pile trace originale et ajoutez la ligne où il a été jeté re, mais apparemment il ne peut être un cadre de pile par appel de méthode .

Conclusion: éviter d'utiliser un jet; et envelopper votre exception dans une nouvelle sur re-lancer -. ce n'est pas laid, il est préférable pratique

Vous pouvez conserver la trace de la pile en utilisant

ExceptionDispatchInfo.Capture(ex);

Voici exemple de code:

    static void CallAndThrow()
    {
        throw new ApplicationException("Test app ex", new Exception("Test inner ex"));
    }

    static void Main(string[] args)
    {
        try
        {
            try
            {
                try
                {
                    CallAndThrow();
                }
                catch (Exception ex)
                {
                    var dispatchException = ExceptionDispatchInfo.Capture(ex);

                    // rollback tran, etc

                    dispatchException.Throw();
                }
            }
            catch (Exception ex)
            {
                var dispatchException = ExceptionDispatchInfo.Capture(ex);

                // other rollbacks

                dispatchException.Throw();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.InnerException.Message);
            Console.WriteLine(ex.StackTrace);
        }

        Console.ReadLine();
    }

La sortie sera quelque chose comme:

Test app ex
Test inner ex
   at TestApp.Program.CallAndThrow() in D:\Projects\TestApp\TestApp\Program.cs:line 19
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 30
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 38
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 47

OK, il semble y avoir un bogue dans le .NET Framework, si vous jetez une exception, et réémettre dans la même méthode, le numéro de ligne d'origine est perdue (ce sera la dernière ligne de la méthode).

Fortunatelly, un gars intelligent nommé Fabrice MARGUERIE trouvé une solution à ce bogue. Voici ma version, que vous pouvez tester en ce Fiddle .NET .

private static void RethrowExceptionButPreserveStackTrace(Exception exception)
{
    System.Reflection.MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
      System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    preserveStackTrace.Invoke(exception, null);
      throw exception;
}

Maintenant, attraper l'exception comme d'habitude, mais au lieu de lancer; il suffit d'appeler cette méthode, et le tour est joué, le numéro de ligne d'origine sera conservé!

Je ne sais pas si cela est par la conception, mais je pense qu'il a toujours été comme ça.

Si le jet nouvelle Exception d'origine est une méthode distincte, le résultat pour lancer doit avoir le nom de la méthode originale et le numéro de ligne, puis le numéro de ligne dans la principale

où l'exception est relancées.

Si vous utilisez un jet ex, alors le résultat sera juste la ligne principale où l'exception est rethrow.

En d'autres termes, jeter ex perd tous (par exemple des détails sur les méthodes de niveau inférieur) stacktrace, alors jeter conserve la trace de la pile historique . Mais si votre exception est générée par la même méthode que votre rethrow, alors vous pouvez perdre des informations.

NB. Si vous écrivez un programme de test très simple et petit, le cadre peut parfois optimiser les choses et changer une méthode pour être code en ligne qui signifie que les résultats peuvent différer d'un programme « réel ».

Est-ce que vous voulez que votre numéro de ligne droite? Il suffit d'utiliser un try / catch par méthode. Dans les systèmes, bien ... juste dans la couche d'interface utilisateur, pas dans la logique ou l'accès aux données, ce qui est très gênant, parce que si vous avez besoin d'opérations de base de données, eh bien, ils ne devraient pas être dans la couche d'interface utilisateur, et vous n'aurez pas le numéro de ligne droite, mais si vous en avez pas besoin, ne pas réémettre avec ni sans exception des prises ...

5 minutes Exemple de code:

Menu Fichier -> Nouveau projet , placez trois boutons, et appelez le code suivant dans chacun:

private void button1_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithoutTC();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

private void button2_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithTC1();
    }
    catch (Exception ex)
    {
            MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

private void button3_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithTC2();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

Maintenant, créez une nouvelle classe:

class Class1
{
    public int a;
    public static void testWithoutTC()
    {
        Class1 obj = null;
        obj.a = 1;
    }
    public static void testWithTC1()
    {
        try
        {
            Class1 obj = null;
            obj.a = 1;
        }
        catch
        {
            throw;
        }
    }
    public static void testWithTC2()
    {
        try
        {
            Class1 obj = null;
            obj.a = 1;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

Exécuter ... le premier bouton est beau!

Je pense que cela est moins un cas de trace de la pile et le changement plus à voir avec la façon dont le numéro de ligne de la trace de la pile est déterminée. L'essayer dans Visual Studio 2010, le comportement est similaire à ce que vous attendez de la documentation MSDN: « jeter ex; » reconstitue la trace de la pile du point de cette déclaration, « jeter; » feuilles la trace de la pile comme comme, sauf que si jamais l'exception est relancée, le numéro de ligne est l'emplacement du rethrow et non l'appel l'exception est venu à travers.

avec "jet;" l'arbre d'appel de la méthode reste inchangée, mais les numéros de ligne peut changer.

Je suis tombé à quelques reprises, et il peut être par la conception et tout simplement pas pleinement documenté. Je peux comprendre pourquoi ils ont fait ce que l'emplacement de rethrow est très utile de savoir, et si vos méthodes sont assez simples la source originale serait habituellement évidente de toute façon.

Comme beaucoup d'autres l'ont dit, qu'il valait mieux ne pas attraper généralement l'exception, sauf si vous avez vraiment, et / ou que vous allez traiter à ce moment-là.

note intéressante:. Visual Studio 2010 ne me laisse pas même construire le code tel que présenté dans la question qu'il ramasse la division par zéro erreur au moment de la compilation

C'est parce que vous Attrapé le Exception de Ligne 12 et ont relancée sur Ligne 15 , de sorte que la trace de pile qu'il faut en espèces, que la Exception a été jeté il.

Pour de meilleures exceptions poignée, vous devez simplement utiliser try...finally, et laisser la bulle non gérée Exception vers le haut.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top