C#

Difference Between “throw” vs “throw ex” in C#

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:

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#

Bartosz Jarmuż

View Comments

  • Thank you guys please keep this up.

    I am always looking forward to each and every article that you send.

  • 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();
    }

Share
Published by
Bartosz Jarmuż

Recent Posts

Flags Attribute For Enum in C#

In this article, we are going to learn about the Flags attribute for enum in…

Aug 10, 2022

Code Maze Weekly #133

Issue #133 of the Code Maze weekly. Check out what's new this week and enjoy…

Updated Date Aug 5, 2022

Type Checking and Type Casting in C#

In this article, we are going to learn various ways of converting a value from…

Updated Date Aug 5, 2022

Sort Dictionary by Value in .NET

In this article, we are going to learn how to sort the values in the…

Updated Date Aug 4, 2022

Blazor WebAssembly Exception Handling With Error Boundaries

In this article, we will look at how to handle unexpected errors in Blazor WebAssembly…

Updated Date Aug 3, 2022

LINQ Improvements in .NET

In this article, we will discuss the new LINQ improvements introduced in .NET 6.  We…

Updated Date Aug 2, 2022