Skip to content
View Article Network

A Brief Discussion on the Differences Between throw and throw ex

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

Generally, when handling Exceptions, we use try...catch to catch and process them. Within a catch block, you might sometimes see the syntax throw; and throw ex;. Although they look similar, there is a significant difference in their actual runtime effects.

How to use throw and throw ex

Using throw to re-throw an Exception

Using throw allows you to re-throw 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 the position of throw ex on line 5, making it impossible to find the line where the error actually occurred.

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

TIP

Actually, the main purpose of this article was to test whether the InnerException of an Exception re-thrown using throw ex would be the original Exception. I originally thought it might be, but the actual test results show that InnerException is null, which means the original stack information is indeed lost.

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

Correct Exception handling approach

In some scenarios, we may need to throw a new Exception to further encapsulate the original Exception. In this case, we should throw a new Exception and pass the original Exception as the inner Exception. Doing 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 an InnerException to preserve information
    throw new CustomException("Additional information", ex);
}

Avoid unnecessary try...catch

If there is no processing within a catch block other than throw, as shown in the code below, you can actually omit the try...catch because such a catch block is meaningless.

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

Change Log

  • 2025-07-31 Initial document creation.