In this article, we are going to discuss a subtle, but hugely important subject – the difference between throw
and throw ex
. Understanding this simple distinction might save a significant amount of debugging time and stress.
Let’s get to it.
Rethrowing the Original Exception – The Standard Way
We’re all familiar with this scenario – we have some code that might throw an exception. We want to act upon this exception (a.k.a. handle it), for example by cleaning up some resources or logging certain data. This does not mean we want to hide the exception – quite contrary, we want it to bubble up, that’s why we want to rethrow the exception:
public class BusinessWorker { public void Work_Throw() { try { //lots of other business logic all around... new ThirdPartyComponent().DoInternalWork(); } catch (Exception ex) { //here we would handle 'ex' in the BusinessWorker (clean up resources, log state, call 911 etc.) throw; } } }
This is the standard way of doing it, and we can be certain that any code that uses our BusinessWorker
will receive all the available details about an exception:
public void ThrowBehaviour_KeepsProperStackTrace() { try { new BusinessWorker().Work_Throw(); } catch (Exception ex) { Assert.AreEqual( @"System.InvalidOperationException: That's a nasty bug! at ThrowVsThrowEx.ThirdPartyComponent.<GoDeeper>g__DoDangerousOperation|1_0() at ThrowVsThrowEx.ThirdPartyComponent.GoDeeper() at ThrowVsThrowEx.ThirdPartyComponent.DoInternalWork() at ThrowVsThrowEx.BusinessWorker.Work_Throw() at ThrowVsThrowEx.ThrowVsThrowExSamples.ThrowBehaviour_KeepsProperStackTrace()", ex.ToString()); } }
We see the exception and we immediately know that something has happened deep within a 3rd party component.
Rethrowing the Original Exception – The Incorrect* Way
Now, what would happen if our code was only slightly different:
public class BusinessWorker { public void Work_ThrowEx() { try { //lots of other business logic all around... new ThirdPartyComponent().DoInternalWork(); } catch (Exception ex) { //here we would handle 'ex' in the BusinessWorker (clean up resources, log state, call 911 etc.) throw ex; } } }
Here we have used the throw ex
statement instead of throw
. The effect on the stack trace of our exception is dramatic:
public void ThrowEx_DropsTheStackTrace() { try { new BusinessWorker().Work_ThrowEx(); } catch (Exception ex) { Assert.AreEqual( @"System.InvalidOperationException: That's a nasty bug! at ThrowVsThrowEx.BusinessWorker.Work_ThrowEx() at ThrowVsThrowEx.ThrowVsThrowExSamples.ThrowEx_DropsTheStackTrace()", ex.ToString()); } }
Yes, dramatic is not an exaggeration, if we consider the time spent on looking for a nasty bug in the BusinessWorker.Work()
method, instead of knowing right away where it happened.
The asterisk after “incorrect” is not accidental though. There are valid situations where this could be the desired behavior.
Wrapping The Original Exception And Throwing A New One
There is one more widely popular method for handling exceptions, especially in large systems. Exceptions can be wrapped into custom domain-specific exception types, in order to make debugging simpler in complex scenarios:
public class BusinessWorker { public void Work_WrapAndThrowNewEx() { try { //lots of other business logic all around... new ThirdPartyComponent().DoInternalWork(); } catch (Exception ex) { //here we would handle 'ex' in the BusinessWorker (clean up resources, log state, call 911 etc.) throw new BusinessException("I am a business domain wrapper for internal exceptions.", ex); } } }
In this case, we are wrapping the original exception into a new exception type – BusinessException
. What happens with our stack trace when we throw a new exception?
public void WrapAndThrowNewEx_KeepsTheStackTraceInTheInnerException() { try { new BusinessWorker().Work_WrapAndThrowNewEx(); } catch (Exception ex) { //the stack trace of the top level exception is short Assert.AreEqual(@" at ThrowVsThrowEx.BusinessWorker.Work_WrapAndThrowNewEx() at ThrowVsThrowEx.ThrowVsThrowExSamples.WrapAndThrowNewEx_KeepsTheStackTraceInTheInnerException()", ex.StackTrace); //however, the actual exception and it's stack trace is visible within the ex.InnerException property //the full exception string also reveals all the layers of inner exceptions Assert.AreEqual( @"ThrowVsThrowEx.BusinessWorker+BusinessException: I am a business domain wrapper for internal exceptions. ---> System.InvalidOperationException: That's a nasty bug! at ThrowVsThrowEx.ThirdPartyComponent.<GoDeeper>g__DoDangerousOperation|1_0() at ThrowVsThrowEx.ThirdPartyComponent.GoDeeper() at ThrowVsThrowEx.ThirdPartyComponent.DoInternalWork() at ThrowVsThrowEx.BusinessWorker.Work_WrapAndThrowNewEx() --- End of inner exception stack trace --- at ThrowVsThrowEx.BusinessWorker.Work_WrapAndThrowNewEx() at ThrowVsThrowEx.ThrowVsThrowExSamples.WrapAndThrowNewEx_KeepsTheStackTraceInTheInnerException()", ex.ToString()); } }
That’s right – we are not losing any information, however, some of the data is now segregated into the InnerException
of the BusinessException
.
When To Use Which Approach?
A rule of thumb for most cases would be to use the approach that does not hide the important debugging info – therefore, either go with “throw
” or, if needed, wrap the exception. The exceptional case would be when we do not want to reveal the actual stack trace.
This is however an edge case and there are other measures that can be taken to prevent inner details from leaking.
Conclusion
We have briefly touched upon three basic methods for rethrowing an exception in C# and what implications each approach has. For a more general overview, see our article on Basics of Handling Exceptions In C#.
Hi. Please tag your code so that Google Translate does not translate it.
Fixed that for you. Thank you very much for the suggestion! Much appreciated.
or you can use the attribute translate=”no”
Yeah, there are several ways to do it. We’ve added a class that google translate looks for. Does it work for you now?
No. This page is translated entirely, including code blocks.
Can you try clearing the cache with Ctrl + F5?
Ok. Now the code is not translated. Thanks.
Excellent! Ty for the suggestion.
You’re welcome)
There is a slight issue with “throw”: It will alter the line-number of the current stack-row. Thus if you have two calls of “DoInternalWork()” within the try-catch, you will not know which of the two calls caused the error. The line-number will just point to the “throw;” and not to the call of “DoInternalWork()”. The Stack inside of “DoInternalWork()” will still be there. But you will not know the line-number, the call originated from.
Same issue if you have a NullReferenceException from within the try-catch (without calling any other methods). After “throw”, you will not be able to see the line number it originated from.
So you should just always wrap the exception into a new one.
Or, if you really want to have a “throw;”, use this extension by calling “e.Rethrow();”
public static void Rethrow(this Exception e) {
ExceptionDispatchInfo.Capture(e).Throw();
}
Good “catch” Andreas, no pun intended 🙂
Thank you guys please keep this up.
I am always looking forward to each and every article that you send.
Hey Zabron,
Thank you so much for the kind words. We’ll do our best!