Incorrect stacktrace by rethrow

2019-01-06 12:10发布

I rethrow an exception with "throw;", but the stacktrace is 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();
}

The right stacktrace should be:

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

But I get:

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

But line 15 is the position of the "throw;". I have tested this with .NET 3.5.

12条回答
聊天终结者
2楼-- · 2019-01-06 12:25

Edit/Replace

The behavior is actually different, but subtilely so. As for why the behavior if different, I'll need to defer to a CLR expert.

EDIT: AlexD's answer seems to indicate that this is by design.

Throwing the exception in the same method that catches it confuses the situation a little, so let's throw an exception from another method:

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

If throw; is used, the callstack is (line numbers replaced with code):

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

If throw ex; is used, the callstack is:

at Main():line (throw ex;)

If exception is not caught, the callstack is:

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

Tested in .NET 4 / VS 2010

查看更多
劳资没心,怎么记你
3楼-- · 2019-01-06 12:27

Throwing twice in the same method is probably a special case - I've not been able to create a stack trace where different lines in the same method follow each other. As the word says, a "stack trace" shows you the stack frames that an exception traversed. And there is only one stack frame per method call!

If you throw from another method, throw; will not remove the entry for Foo(), as expected:

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

If you modify Rethrower() and replace throw; by throw ex;, the Foo() entry in the stack trace disappears. Again, that's the expected behavior.

查看更多
祖国的老花朵
4楼-- · 2019-01-06 12:27

That is because you catched the Exception from Line 12 and have rethrown it on Line 15, so the Stack Trace takes it as cash, that the Exception was thrown from there.

To better handle exceptions, you should simply use try...finally, and let the unhandled Exception bubble up.

查看更多
贪生不怕死
5楼-- · 2019-01-06 12:28

How can I preserve the REAL stacktrace?

You throw a new exception, and include the original exception as the inner exception.

but that's Ugly... Longer... Makes you choice the rigth exception to throw....

You are wrong about the ugly but right about the other two points. The rule of thumb is: don't catch unless you are going to do something with it, like wrap it, modify it, swallow it, or log it. If you decide to catch and then throw again, make sure you are doing something with it, otherwise just let it bubble up.

You may also be tempted to put a catch simply so you can breakpoint within the catch, but the Visual Studio debugger has enough options to make that practice unnecessary, try using first chance exceptions or conditional breakpoints instead.

查看更多
成全新的幸福
6楼-- · 2019-01-06 12:31

I think this is less a case of stack trace changing and more to do with the way the line number for the stack trace is determined. Trying it out in Visual Studio 2010, the behaviour is similar to what you would expect from the MSDN documentation: "throw ex;" rebuilds the stack trace from the point of this statement, "throw;" leaves the stack trace as it as, except that where ever the exception is rethrown, the line number is the location of the rethrow and not the call the exception came through.

So with "throw;" the method call tree is left unaltered, but the line numbers may change.

I've come across this a few times, and it may be by design and just not documented fully. I can understand why they may have done this as the rethrow location is very useful to know, and if your methods are simple enough the original source would usually be obvious anyway.

As many other people have said, it usually best to not catch the exception unless you really have to, and/or you are going to deal with it at that point.

Interesting side note: Visual Studio 2010 won't even let me build the code as presented in the question as it picks up the divide by zero error at compile time.

查看更多
男人必须洒脱
7楼-- · 2019-01-06 12:32

This is a well known limitation in the Windows version of the CLR. It uses Windows' built-in support for exception handling (SEH). Problem is, it is stack frame based and a method has only one stack frame. You can easily solve the problem by moving the inner try/catch block into another helper method, thus creating another stack frame. Another consequence of this limitation is that the JIT compiler won't inline any method that contains a try statement.

查看更多
登录 后发表回答