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.

To download the source code for this article, you can visit our GitHub repository.

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:

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!
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#

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!