C# rethrow an exception: how to get the exception

2020-03-09 08:45发布

问题:

There has been discussion here before about the correct way to rethrow an exception. This question, instead, is about how to get useful behavior from Visual Studio when using rethrow.

Consider this code:

   static void foo() {
        throw new Exception("boo!");
    }

    static void Main(string[] args) {
        try {
            foo();
        } catch (Exception x) {
            // do some stuff
            throw;
        }
    }

The exception that comes out has the correct stack trace, showing foo() as the source of the exception. However, the GUI Call Stack window only shows Main, whereas I was expecting it to show the exception's call stack, all the way to foo.

When there is no rethrow, I can use the GUI to very quickly navigate the call stack to see what call caused the exception and how we got there.

With the rethrow I'd like to be able to do the same thing. Instead, the call stack that the GUI shows is of no use to me. I have to copy the exception details to the clipboard, paste it to Notepad, and then manually navigate to whichever function of the call stack I'm interested in.

By the way, I get the same behavior if I add [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] or if I change the catch to just catch (Exception).

My question is: given that the code I have uses rethrow, can someone suggest a convenient way to navigate the call stack associated with the exception? I'm using Visual Studio 2010.

回答1:

The debugger breaks at the throw in Main because that exception is unhandled. By default, the debugger will only break on unhandled exceptions. Once you've stopped at Main, the call stack for the original exception from foo is present in the exception, but all of the other context has been lost (e.g. locals, stack/memory state).

It sounds like you want the debugger to break on the throw in foo, so you should tell the debugger to break on first-chance exceptions:

  1. Debug » Exceptions... (Ctrl+Alt+E)
  2. Check "Thrown" for the exception types you care about (in this case, Commange Language Runtime Exceptions)
  3. Click OK
  4. Start debugging

In this case, the debugger will break immediately when foo throws an exception. Now, you can examine the stack, locals, etc., in the context of the original exception. If you continue execution (F5), the debugger will break again on the rethrow in Main.

Taking another approach, if you're running VS2010 Ultimate, you can also use IntelliTrace to "debug backwards" to see parameters, threads, and variables at the time of the exception. See this MSDN article for details. (Full disclosure: I work on a team closely related to IntelliTrace).



回答2:

If you use ReSharper, you can copy exception stacktrace to clipboard, then choose in the menu: ReSharper > Tools > Browse Stack Trace (Ctrl+E,T). It will show stacktrace with clickable locations, so you'll be able to quickly navigate.


(source: jetbrains.com)

This feature is also very useful while digging through logs from users (if stacktraces of exceptions are logged).



回答3:

Not that you should re-throw but here's a blog post about how to preserve the stack trace, essentially it boils down to this:

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

...
catch (Exception ex)
{
  // do something
  // ...
  PreserveStackTrace(ex);
  throw;
}


回答4:

Mike Stall has given a great and simple solution to your problem:

Mark the methods where you rethrow the exception with the attribute [DebuggerNonUserCode]

The IDE will consider this is not your code and will not break the debugger in such place, and instead will look further in the stack, showing the next rethrow or the initial exception place.

(if the next rethrow is also annoying, mark it as [DebuggerNonUserCode] as well, etc...)