A few days ago I stumbled across a C# code that was rethrowing the exception by means of passing captured exception object as an argument of the throw keyword (throw ex;
). Such a pattern of rethrowing the exception can turn an exercise of troubleshooting a production issue into a game of “Find where the exception happened.”. I had to play this game a few times in my life and it is not the most fun game to play, especially if your codebase is quite large. So, here I want to take a minute and discuss what is the correct way to rethrow the exception in .Net.
throw ex
Suppose you have a code that catches the exception (line 21), logs the necessary bits (line 23), rethrows the exception (line 24) and then the stack trace from the rethrown exception is outputted to the console in the main method (line 11).
The output of this program would be:
at CSharpException.Program.DoSomeUsefulWork() in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 24
at CSharpException.Program.Main(String[] args) in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 9
This could be quite unexpected. By looking at this output it seems like the exception was thrown on line 24 of DoSomeUsefulWork
method. There is no mentioning of the ICanThrowException
method or any signs that it was ever invoked. In this simple example, it might be easy to figure out what actually happened, but imagine if this is a real product code with hundreds of lines of complicated logic, in that case, it would take a lot of effort or might not be even possible to restore the complete chain of events that lead to the exception.
But why does it happen like this? Well, in C# when you want to throw the exception, you have to pass the exception object itself, that is usually done by instantiating a new instance of the class that represents the exception throw new Exception()
(line 30). The thing is that for C# compiler it looks the same if you instantiate a new exception object or if you pass an already existing object. This can be verified by looking at the IL code generated from the example. Here is the IL code for ICanThrowException
method:
On line 7 a new instance of the Exception class is created and the reference to it is placed on the stack. On line 8 the exception is thrown by the call to throw
command.
The code for the catch block of the DoSomeUsefulWork
method looks almost the same in terms of throwing the exception:
Here on line 3 stloc.0
command stores the exception reference to the local variable, on line 8 ldloc.0
pushes the reference to the top of the stack and then throw
command actually throws the exception. In ICanThrowException
a brand new instance of an exception object is created and of course, CLR auto-populates the stack trace property of that exception. But now in the catch block of the DoSomeUsefulWork
method, the same throw
command is used and CLR will re-initialize the stack trace property of the exception that was passed to the throw
instruction as if it was a brand new exception instantiated right there and make it look like the exception is thrown in the catch block itself.
The easiest way to rethrow the exception without losing the original stack trace is to just do throw
and don’t pass the exception object. Here is the same DoSomeUsefulWork
method that just does throw
:
Notice that on line 11 throw
instruction does not have any arguments. Here is the output of the program:
at CSharpException.Program.ICanThrowException() in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 30
at CSharpException.Program.DoSomeUsefulWork() in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 24
at CSharpException.Program.Main(String[] args) in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 9
This makes much more sense, now it is clearly visible that the exception happened on line 30 of ICanThrowException
method and was rethrown on line 24 of the DoSomeUsefulWork
method. The change is visible in the IL code as well
On the line 8 rethrow
instruction is used instead of the throw
. This instruction preserves exception’s place of origin but it still has its flaws.
While it is true that throw
preserves the information on where the exception was thrown, but it still does not give a complete picture of what happened. A careful reader might have noticed that in DoSomeUsefulWork
method there are actually two calls to ICanThrowException
method. This is not a typo, imagine that DoSomeUsefulWork
is a fairly complex method with few if
statements each of which can call to ICanThrowException
. Now, the question is which one led to the exception? Unfortunately, throw
does not give an answer to that. In the previous example, the only mentioning of the DoSomeUsefulWork
method was line 24 and this is where the exception was rethrown, so it is unclear if it occurs on line 18 or line 19 (see the first code sample). In .Net Framework 4.5 ExceptionDispatchInfo class was introduced that can solve this issue. It is intended to be used by the TPL library, specifically to support throwing non-aggregated exceptions with await
keyword. But since the class is public it can be used to rethrow exception even without TPL. Here is how it looks in the code (line 11):
It works by first capturing the exception and then throwing it again. Here is the output of the program:
at CSharpException.Program.ICanThrowException() in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 30
at CSharpException.Program.DoSomeUsefulWork() in C:\UsersSource\Repos\CSharpException\CSharpException\Program.cs:line 18
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at CSharpException.Program.DoSomeUsefulWork() in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 24
at CSharpException.Program.Main(String[] args) in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 8
This is much better, not only it shows where the exception originated and where it was rethrown, but it also shows the complete invocation chain that led to the exception, making it much easier to deduct from the logs where and why the issue happened. The internals of ExceptionDispatchInfo is quite simple when Capture
is called the new instance of ExceptionDispatchInfo is created and it holds a reference to the exception object that is passed as a parameter. When Throw
is called it restores the stack trace of the original exception and then basically does a throw ex
.
One other way to preserve the stack trace information of the original exception on rethrow is to wrap the original exception with another exception, like this:
The output would be somewhat similar to the ExceptionDispatchInfo usage
System.Exception: Rethrown ---> System.Exception: Bad thing happened
at CSharpException.Program.ICanThrowException() in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 30
at CSharpException.Program.DoSomeUsefulWork() in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 18
--- End of inner exception stack trace ---
at CSharpException.Program.DoSomeUsefulWork() in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 24
at CSharpException.Program.Main(String[] args) in C:\Users\Source\Repos\CSharpException\CSharpException\Program.cs:line 9
But, for this to work the main method should also be changed to log the entire exception Console.WriteLine(ex)
or to extract the stack trace from the inner exception in addition to the main exception stack trace. It can be an option if ExceptionDispatchInfo
is not available, but generally, it is better to use ExceptionDispatchInfo
when possible to rethrow.
In general, in .Net I would strongly advise against using throw ex
to just rethrow the exception in a catch block as it destroys the information about where the exception was thrown originally and will definitely cause the frustration when looking into the logs and trying to figure out what had happened. With .Net Framework 4.5 and higher I would always use ExceptionDispatchInfo to rethrow as it gives the most complete picture of the events that happened. On versions of the framework lower than 4.5, I would just use throw
as the simplest way to rethrow and to keep the information on the origin of the exception. Wrapping the exception with another exception to keep the information on what method led to the exception is simply not worth it.
Happy coding and hope you’ll not have to play “Find where the exception happened.” game ever!