Preserving a stacktrace when rethrowing exception with ExceptionDispatchInfo

One of the annoyances with Exception handling in C# is that it is easy to stomp the stacktrace.

Now everyone who has been in .NET for a while knows you can call throw inside a catch block to rethrow the exception properly...

public void ThisWorks()  
{
    try
    {
        throw new Exception("I failed...");
    }
    catch(Exception ex)
    {
        // do something here...

        // rethrow to preserve info
        throw;
    }
}

But what happens if you error handling is more complicated and you can't rethrow the exception until later in your code?

The most obvious scenario is with multi-threaded applications. An exception on one thread can be caught and triggers the other threads to stop doing their thing. Once everyone is in a happy place you can then rethrow your exception in the caller.

Here's a contrived and simple example minus any concept of threading:

public void GetsStomped()  
{
    Exception myEx;
    try
    {
        throw new Exception("I failed...");
    }
    catch(Exception ex)
    {
        // collect the error for use later
        myEx = ex;
    }

    // do some more stuff that you can't do in the catch block

    // now we want to rethrow the error and watch our stacktrace get stomped
    if(myEx != null)
        throw myEx;
}

Obviously that's no good. We'll lose our info and need to dig into the InnerException to see what really happened.

Previously to get around this, could set the internal property InternalPreserveStackTrace on your exception via Reflection. But now in .NET 4.5 we have a new toy.

Meet the ExceptionDispatchInfo class! This class captures the state of an exception and allows us to rethrow it while preserving the original state!

public void Woohoo()  
{
    Exception myEx;
    try
    {
        throw new Exception("I failed...");
    }
    catch(Exception ex)
    {
        // collect the error for use alter
        myEx = ex;
    }

    // do some more stuff

    // now we want to rethrow the error
    ExceptionDispatchInfo.Capture(myEx).Throw();
}

This results in a stack trace that includes the original context as well as the new trace:

Unhandled Exception: System.Exception: I failed...  
   at Exceptions.Program.Woohoo() in Program.cs:line 58
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Exceptions.Program.Woohoo() in Program.cs:line 69
   at Exceptions.Program.Main(String[] args) in Program.cs:line 14
comments powered by Disqus