C# 通过重新刷新进行的堆栈跟踪不正确

C# 通过重新刷新进行的堆栈跟踪不正确,c#,stack-trace,throw,rethrow,C#,Stack Trace,Throw,Rethrow,我使用“throw;”重新引发异常,但stacktrace不正确: static void Main(string[] args) { try { try { throw new Exception("Test"); //Line 12 } catch (Exception ex) { throw; //Line 15 } } catch (Exception

我使用“throw;”重新引发异常,但stacktrace不正确:

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();
}
正确的堆栈跟踪应该是:

但我得到:


但第15行是“投掷”的位置。我已经用.NET 3.5对此进行了测试。

这是因为您从第12行捕获了
异常
,并在第15行重新捕获它,因此堆栈跟踪将其作为现金,从那里抛出
异常


为了更好地处理异常,您应该简单地使用
try…finally
,让未处理的
异常出现。

在同一个方法中抛出两次可能是一种特殊情况-我无法创建堆栈跟踪,其中同一方法中的不同行相互跟随。正如这个词所说,“堆栈跟踪”向您显示异常遍历的堆栈帧。每个方法调用只有一个堆栈帧

如果从另一个方法抛出,
throw不会删除
Foo()
的条目,如预期:

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

如果修改
Rethrower()
并替换
throw
抛出
,堆栈跟踪中的
Foo()
项消失。同样,这是预期的行为。

不确定这是否是出于设计,但我认为一直都是这样

如果原始抛出新异常位于单独的方法中,则抛出的结果应具有原始方法名称和行号,然后是重新抛出异常的main中的行号

如果您使用throw ex,那么结果将只是main中的一行,其中异常是rethrow

换句话说,throw ex会丢失堆栈跟踪的all,而throw会保留堆栈跟踪的history(即较低级别方法的详细信息)。但是,如果您的异常是通过与rethrow相同的方法生成的,那么您可能会丢失一些信息


注意。如果您编写了一个非常简单的小型测试程序,框架有时可以优化事情,并将方法更改为内联代码,这意味着结果可能与“真实”程序不同

这是Windows版CLR中众所周知的限制。它使用Windows内置的异常处理支持(SEH)。问题是,它是基于堆栈帧的,并且一个方法只有一个堆栈帧。通过将内部try/catch块移动到另一个helper方法中,从而创建另一个堆栈帧,可以轻松解决此问题。此限制的另一个后果是JIT编译器不会内联任何包含try语句的方法。

Edit/Replace

这种行为实际上是不同的,但微妙地如此。至于为什么行为不同,我需要听从CLR专家的意见

编辑:似乎表明这是出于设计

在捕获异常的同一个方法中抛出异常会稍微混淆情况,因此让我们从另一个方法中抛出异常:

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;
    }
}
如果
抛出时,调用堆栈为(行号替换为代码):

如果
抛出ex时,调用堆栈为:

at Main():line (throw ex;)
at Throw():line (int b = 10 / a;)
at Main():line (Throw())
如果未捕获异常,则调用堆栈为:

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

在.NET 4/VS 2010中进行了测试,这是可以被认为是意料之中的事情。 如果指定
throw-ex,则修改堆栈跟踪是常见的情况,FxCop将通知您堆栈已修改。如果你做
抛出,未生成警告,但仍将修改跟踪。
所以不幸的是,现在最好不要抓住前男友,或者把他当作一个内奸扔掉。
我认为它应该被视为是一个Windows影响或类似的smth-编辑。
杰夫·里克特(Jeff Richter)在他的“CLR通过C#”中更详细地描述了这种情况。

下面的代码抛出相同的错误 它捕获的异常对象,并且 使CLR重置其启动 例外点:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw e; // CLR thinks this is where exception originated.
    // FxCop reports this as an error
  }
}
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
  }
}
相反,如果你再次抛出 通过使用抛出 关键字本身,CLR不会 重置堆栈的起点。这个 下面的代码重新抛出相同的 它捕获的异常对象, 导致CLR不重置其 异常的起点:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw e; // CLR thinks this is where exception originated.
    // FxCop reports this as an error
  }
}
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
  }
}
事实上,两者之间的唯一区别 这两个代码片段是 CLR认为是原始位置 抛出异常的位置。 不幸的是,当你投掷或 重新显示异常,Windows不会 重置堆栈的起点。So 如果异常未处理, 报告的堆栈位置 Windows错误报告是 最后一次投掷的位置或 重新抛出,即使CLR知道 原始文件所在的堆栈位置 引发了异常。这是 不幸的是,这会导致调试失败 在中失败的应用程序 这个领域要困难得多。一些 开发人员已经发现这一点 令人无法忍受的是,他们选择了 实现代码的不同方式 确保堆栈跟踪准确无误 反映了 异常最初被抛出:

private void SomeMethod() {
  Boolean trySucceeds = false;
  try {
    ...
    trySucceeds = true;
  }
  finally {
    if (!trySucceeds) { /* catch code goes in here */ }
  }
}
如何保存真正的stacktrace

抛出一个新异常,并将原始异常作为内部异常包含

但那太难看了。。。比较长的使您选择要抛出的rigth异常

你对丑陋的看法是错误的,但对另外两点的看法是正确的。经验法则是:除非你打算用它做些什么,比如包装它、修改它、吞下它或记录它,否则不要抓。如果你决定捕捉
,然后再次抛出
,请确保你正在用它做些什么,否则就让它冒出来


您可能还想简单地放置一个catch,以便可以在catch中设置断点,但Visual Studio调试器有足够的选项使这种做法变得不必要,请尝试使用第一次机会异常或条件断点。

我认为这不是堆栈跟踪更改的情况
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;
        }
    }
}
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;
}
ExceptionDispatchInfo.Capture(ex);
    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();
    }
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