On this page

Skip to content

A Brief Discussion on the Differences Between throw and throw ex

This was a common point of confusion in the early days, and while it is considered basic knowledge in recent years, it seems some colleagues are still unaware of it. I decided to put together a brief summary, partly because I sometimes like to write about simpler topics.

When handling Exceptions, we typically use try...catch to catch and process them. Inside a catch block, you may sometimes see the statements throw; and throw ex;. Although they look similar, there is a significant difference in their actual runtime behavior.

How to use throw and throw ex

Using throw to re-throw an Exception

Using throw re-throws the caught Exception while preserving the original Exception stack information. This is crucial for debugging because it provides the correct path where the Exception occurred.

csharp
try {
    try {
        int result = Divide(1, 0);
    } catch {
        throw;
    }
} catch (Exception e) {
    Console.WriteLine(e.ToString());
}

static int Divide(int numerator, int denominator) {
    return numerator / denominator;
}

The resulting error message is as follows, clearly indicating that the error occurred on line 3.

text
System.DivideByZeroException: Attempted to divide by zero.
   at Program.<<Main>$>g__Divide|0_0(Int32 numerator, Int32 denominator) in D:\Programming\Projects\TestThrow\TestThrow\Program.cs:line 12
   at Program.<Main>$(String[] args) in D:\Programming\Projects\TestThrow\TestThrow\Program.cs:line 3

Problems caused by using throw ex

Using throw ex creates and throws a new Exception, which resets the Exception's stack information, making it much harder to trace the Exception.

csharp
try {
    try {
        int result = Divide(1, 0);
    } catch (Exception ex) {
        throw ex;
    }
} catch (Exception e) {
    Console.WriteLine(e.ToString());
}

static int Divide(int numerator, int denominator) {
    return numerator / denominator;
}

The displayed error message is as follows. You can see that in the stack information, the error location has changed to line 5, where throw ex is called, making it impossible to find the line where the error actually originated.

csharp!
System.DivideByZeroException: Attempted to divide by zero.
   at Program.<Main>$(String[] args) in D:\Programming\Projects\TestThrow\TestThrow\Program.cs:line 5

TIP

The main purpose of this article was actually to test whether the InnerException of an Exception re-thrown via throw ex would be the original Exception. I initially thought it might be, but actual testing shows that InnerException is null, which confirms that the original stack information is indeed lost.

Perhaps because throw ex was misused so often, starting from .NET 5, the CA2200 analysis rule was added. When code containing throw ex is detected, a warning CA2200: Rethrow to preserve stack details is triggered. For more information on CA2200, please refer to Breaking change: CA2200: Rethrow to preserve stack details and CA2200: Rethrow to preserve stack details.

Correct Exception handling

In some scenarios, we may need to throw a new Exception to further encapsulate the original Exception. In such cases, we should throw a new Exception and pass the original Exception as the inner Exception. This preserves the original Exception's information while adding new contextual information.

csharp
public class CustomException : Exception {
    public CustomException(string message, Exception innerException)
        : base(message, innerException) {
    }
}

try {
    // Code that might throw an Exception...
} catch (Exception ex) {
    // ex must be passed as the InnerException to preserve information
    throw new CustomException("Additional information", ex);
}

Avoiding unnecessary try...catch

If there is no processing inside a catch block other than throw, as shown in the code below, you can actually omit the try...catch entirely, as such a catch block serves no purpose.

csharp
try {
    // Code that might throw an Exception...
} catch {
    // No processing done, only throw
    throw;
}

Change Log

  • 2025-07-31 Initial document creation.