As long as there has been software, there have been bugs. Exceptions are a hierarchy of classes designed to help developers capture, inspect, and handle errors. For C# developers and many other languages, the way we work with exceptions is in the try/catch
construct.
In this post, we’ll look at ExceptionDispatchInfo
, a unique supplement to a .NET developer’s exception handling toolbox.
The Good Old Try/Catch Block
As .NET developers, we’re all familiar with the try/catch
format of handling exceptions.
try {
A();
}
catch (Exception e)
{
throw;
}
When our application throws an exception from the calling of method A
, we know that the catch
block will capture that exception. It’s a fundamental understanding of all .NET developers.
There are a few caveats with this approach.
- We can choose to handle the exception, thus not letting the exception bubble any further.
- We can choose to handle specific exceptions, like
NotImplementedException
orArgumentException
. - We can choose to remove all stack information by replacing
throw
withthrow e
. In a sense, reestablishing the location of the exception.
These all have valid use cases, but what if I said that we could buffer our exception in a container designed to contain an exception?
The ExceptionDispatchInfo Class
The .NET Framework introduced the ExceptionDispatchInfo
class around version 4.5.
The
ExceptionDispatchInfo
object stores the stack trace information and Watson information that the exception contains at the point where it is captured. The exception can be thrown at another time and possibly on another thread by calling theExceptionDispatchInfo.Throw
method. The exception is thrown as if it had flowed from the point where it was captured to the point where the Throw method is called. –Microsoft
Let’s take a look at an example.
ExceptionDispatchInfo Example
Given we have the following code, we expect our code to throw an exception of NotImplementedException
at runtime. Additionally, we expect to see the methods of A
, B
, and C
in our exception stack information.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
namespace Preview
{
class Program
{
public static void Main(string[] args)
{
try {
A();
}
catch (Exception e)
{
throw;
}
}
public static void A()
{
B();
}
private static void B()
{
C();
}
private static void C()
{
throw new NotImplementedException();
}
}
}
No surprise, we do.
Unhandled exception. System.NotImplementedException: The method or operation is not implemented.
at Preview.Program.C() in /Users/khalidabuhakmeh/Projects/Dotnet/Preview/Program.cs:line 34
at Preview.Program.B() in /Users/khalidabuhakmeh/Projects/Dotnet/Preview/Program.cs:line 29
at Preview.Program.A() in /Users/khalidabuhakmeh/Projects/Dotnet/Preview/Program.cs:line 24
at Preview.Program.Main(String[] args) in /Users/khalidabuhakmeh/Projects/Dotnet/Preview/Program.cs:line 14
How do we use ExceptionDispatchInfo
in this scenario?
class Program
{
public static void Main(string[] args)
{
ExceptionDispatchInfo edi = null;
try {
A();
}
catch (Exception e)
{
// captured on line 15
edi = ExceptionDispatchInfo.Capture(e);
}
// throw again on line 24
edi.Throw();
}
public static void A()
{
B();
}
private static void B()
{
C();
}
private static void C()
{
throw new NotImplementedException();
}
}
What’s surprising is that we see the original stack trace and the line where we throw the exception.
Unhandled exception. System.NotImplementedException: The method or operation is not implemented.
at Preview.Program.C() in /Users/khalidabuhakmeh/Projects/Dotnet/Preview/Program.cs:line 39
at Preview.Program.B() in /Users/khalidabuhakmeh/Projects/Dotnet/Preview/Program.cs:line 34
at Preview.Program.A() in /Users/khalidabuhakmeh/Projects/Dotnet/Preview/Program.cs:line 29
at Preview.Program.Main(String[] args) in /Users/khalidabuhakmeh/Projects/Dotnet/Preview/Program.cs:line 15
--- End of stack trace from previous location ---
at Preview.Program.Main(String[] args) in /Users/khalidabuhakmeh/Projects/Dotnet/Preview/Program.cs:line 24
Conclusion
Awesome! ExceptionDispatchInfo
allows us to store an exception for a period within our AppDomain (the class is not serializable). This storage mechanism can help us to perform clean-up tasks or other necessary steps before throwing an exception. As long as it’s within the same app domain, this class can help us transport and throw an exception. The transport approach can be useful in programming models that have background threads and a main thread.
If you’ve used ExceptionDispatchInfo
in your codebase, I’d love to hear how and where you found it useful.