If you don't need to be convinced that this is a bug, not a feature, you can skip ahead to the workaround.
The Problem
Imagine a program where Method1 calls Method2 calls Method3 calls Method4 which throws an exception. Imagine that one of these intermediate methods, Method2, needs to catch the exception, do some stuff (not shown), and then rethrow the caught exception.
public static void MyMain()
{
try { Method1(); } catch (Exception e) { Console.WriteLine(e); }
}
public static void Method1()
{
Method2();
}
public static void Method2()
{
try { Method3(); } catch (Exception e) { throw e; }
}
public static void Method3()
{
Method4();
}
public static void Method4()
{
throw new Exception("From Method4");
}
The ultimate caller, MyMain, prints the exception, including it's stack trace. Here's what it prints:
System.Exception: From Method4at MiscCS4Tests.PreserveExceptionStackTrace2.Method1()
at MiscCS4Tests.PreserveExceptionStackTrace2.Method2()
at MiscCS4Tests.PreserveExceptionStackTrace2.MyMain()
Wait, what happened to Method3 and Method4?
The problem is that, when C# throws an exception, it throws away (no pun intended) all stack trace information and starts over.
That is never what I want. (And if it was, I would more likely create a new exception object to throw for other reasons.)
There are ways around this:
- Instead of throw e; use throw; That will preserve the stack trace. But this form of throw is only allowed inside a catch block.
- Create & throw a new exception object that nests the caught exception as the "inner exception". That will retain the full stack trace, if you chain through all the nested exceptions (which Exception.ToString does).
As a typical example, suppose that you catch several different explicit exceptions using different catch blocks, but the body of the catch blocks are identical.
try {
AMethod();
} catch (ArgumentException e) {
DoSomething();
throw;
} catch (IOException e) {
DoSomething();
throw;
}
As a good programmer, you consider duplicated code to be evil, so you refactor it into a shared place. Perhaps a separate method; more often inline like this:
try {
AMethod();
}
catch (ArgumentException e) { ex = e; }
catch (IOException e) { ex = e; }
if (ex != null) {
DoSomething();
throw ex;
}
But however you do it, you're no longer in a catch block when you rethrow. So you have to explicitly specify the exception on the throw. And you've lost your stack trace.
The Workaround
It turns out that Microsoft realized that they needed to preserve (remote) stack traces when rethrowing remote exceptions. So they built that functionality into the Exception class. Too bad they didn't realize the rest of us needed it too; so they made it private.
Here's a handy Exception extension method that will use reflection to invoke this private method.
public static class ExceptionHelper
{
public static void PreserveStackTrace(this Exception e)
{
_internalPreserveStackTrace(e);
}
private static readonly Action<Exception> _internalPreserveStackTrace =
(Action<Exception>)Delegate.CreateDelegate(
typeof(Action<Exception>),
typeof(Exception).GetMethod(
"InternalPreserveStackTrace",
BindingFlags.Instance | BindingFlags.NonPublic));
}
Here's Method2 (from the above example) rewritten to use this:
public static void Method2()
{
try { Method3(); }
catch (Exception e) { e.PreserveStackTrace(); throw e; }
}
And here is the stack trace now:
System.Exception: From Method4
at MiscCS4Tests.PreserveExceptionStackTrace2.Method4() in xxx.cs:line 170
at MiscCS4Tests.PreserveExceptionStackTrace2.Method3() in xxx.cs:line 165
at MiscCS4Tests.PreserveExceptionStackTrace2.Method2() in xxx.cs:line 159
at MiscCS4Tests.PreserveExceptionStackTrace2.Method2() in xxx.cs:line 161
at MiscCS4Tests.PreserveExceptionStackTrace2.Method1() in xxx..cs:line 154
at MiscCS4Tests.PreserveExceptionStackTrace2.MyMain() in xxx..cs:line 149
at MiscCS4Tests.PreserveExceptionStackTrace2.Method4() in xxx.cs:line 170
at MiscCS4Tests.PreserveExceptionStackTrace2.Method3() in xxx.cs:line 165
at MiscCS4Tests.PreserveExceptionStackTrace2.Method2() in xxx.cs:line 159
at MiscCS4Tests.PreserveExceptionStackTrace2.Method2() in xxx.cs:line 161
at MiscCS4Tests.PreserveExceptionStackTrace2.Method1() in xxx..cs:line 154
at MiscCS4Tests.PreserveExceptionStackTrace2.MyMain() in xxx..cs:line 149
Note that Method2 appears twice in the trace: once where the exception was caught, and once where it was thrown. This is undesirable but better than losing the rest of the stack trace.
Caveats:
- There are security restrictions for using reflection. Refer to the Accessing Members That Are Normally Inaccessible section in this article.
- This depends on internal implementations of the Exception class which could change. Although, if that happens, you can at least rewrite this extension method to use less efficient, but publicly supported, interfaces as described here. This involves serializing & deserializing the exception.