Rethrowing exceptions and preserving the full call stack trace
Did you know that depending on the way you rethrow exceptions you may lose important information? There are already several blog posts that explain and demonstrate the difference between throw and throw ex. I'm realizing only now that none of the two solutions yields the complete call stack trace information!
Let's see what the problem is and I'll show you the real solution.
I'll use the following method to generate an exception:
private static void BadWork()
{
int i = 0;
int j = 12 / i; // Line 10: DivideByZeroException
int k = j + 1;
}
Let's consider what happens if we call BadWork and rethrow the exception with throw ex as follows:
try
{
BadWork();
}
catch (Exception
ex)
{
// do something
// ...
throw ex; // Line 24
}
Here is the call stack trace that we get in this case:
Unhandled Exception: System.DivideByZeroException:
Attempted to divide by zero.
at
Program.WithThrowEx() in Program.cs:line 24
at Program.Main(String[] args) in Program.cs:line 88
Line 24 is where throw ex is, not where the exception was thrown.
Let's now replace throw ex by throw:
try
{
BadWork();
}
catch
{
// do something
// ...
throw; // Line
38
}
This time, here is the call stack trace:
Unhandled Exception: System.DivideByZeroException:
Attempted to divide by zero.
at Program.BadWork()
in Program.cs:line 10
at
Program.WithThrow() in Program.cs:line 38
at
Program.Main(String[] args) in Program.cs:line 89
As you can see, we get one additional stack frame this time.
Line 10 is where the exception was thrown, which is
important information because this is the only information
that identifies where the exception actually happened.
This shows that it's better to use throw rather than throw ex if you want the full stack trace information to be preserved. However, there are cases where throw is not enough. In the following example, throw does not preserve the full stack trace:
try
{
int i = 0;
int j = 12 / i; // Line 47
int k = j + 1;
}
catch
{
// do something
// ...
throw; // Line 54
}
Unhandled Exception: System.DivideByZeroException:
Attempted to divide by zero.
at
Program.WithThrowIncomplete() in Program.cs:line 54
at Program.Main(String[] args) in
Program.cs:line 106
This time, you can see that information is lost again. Line 54 is where throw is, not where the exception was thrown.
To preserve the full call stack information, you need to use the following method:
private static void PreserveStackTrace(Exception
exception)
{
MethodInfo preserveStackTrace =
typeof(Exception).GetMethod("InternalPreserveStackTrace",
BindingFlags.Instance | BindingFlags.NonPublic);
preserveStackTrace.Invoke(exception, null);
}
This method can be used as follows:
try
{
int i = 0;
int j = 12 / i; //
Line 78
int k = j + 1;
}
catch (Exception
ex)
{
// do something
// ...
PreserveStackTrace(ex);
throw; // Line 86
}
Here is the new call stack information you get with the
above code:
Unhandled Exception: System.DivideByZeroException:
Attempted to divide by zero.
at
Program.WithThrowAndStackTracePreservation() in
Program.cs:line 78
at
Program.WithThrowAndStackTracePreservation() in
Program.cs:line 86
at Program.Main(String[] args)
in Program.cs:line 110
Here is the call stack information you get with throw ex and a call to PreserveStackTrace:
Unhandled Exception: System.DivideByZeroException:
Attempted to divide by zero.
at Program.BadWork()
in Program.cs:line 10
at
Program.WithThrowExAndStackTracePreservation() in
Program.cs:line 62
at
Program.WithThrowExAndStackTracePreservation() in
Program.cs:line 69
at Program.Main(String[] args)
in Program.cs:line 109
It's worth noting that if you call PreserveStackTrace, then you can use throw or throw ex and you'll equally get the full stack trace information.
I found this useful trick on
Chris Taylor's blog. If you want to use this with .NET 1, you should refer to
Chris' post because it seems that the
InternalPreserveStackTrace method didn't exist before
.NET 2.0.
The complete source code is attached to this post.
