Skip to content

A Brief Discussion on the Differences Between throw and throw ex

TLDR

  • In a catch block, you should prioritize using throw; instead of throw ex;.
  • throw; preserves the original Stack Trace, ensuring that you can pinpoint the original line number where the error occurred during debugging.
  • throw ex; treats the current location as the start of the error, causing the original stack information to be lost; it should be avoided.
  • If you need to wrap an error, you should create a new Exception and pass the original Exception as the innerException.
  • If a catch block contains only throw; with no other logic, the try...catch block should be removed entirely.
  • .NET 5 and later versions have introduced the CA2200 rule, which automatically detects and warns against the use of throw ex.

How to use throw and throw ex

Using throw to re-throw an Exception

When you might encounter this: When you need to intercept an error in a catch block, perform some logic, and still want to propagate the error upward without destroying the original error stack.

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

When you might encounter this: When a developer mistakenly believes that throw ex is the standard way to re-throw an error, ignoring the fact that it resets the stack information.

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 throw ex line (line 5), making it impossible to find the actual line where the error 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

Tests show that an Exception re-thrown using throw ex has an InnerException of null, which confirms that the original stack information is indeed lost.

Starting from .NET 5, the official team added the CA2200 check rule. When code containing throw ex is detected, a warning CA2200: Rethrow to preserve stack details will appear. For detailed explanations, please refer to Breaking change: CA2200: Rethrow to preserve stack details and CA2200: Rethrow to preserve stack details.

The correct way to handle Exceptions

When you might encounter this: When you need to wrap the original Exception and add additional business logic context information.

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 context.

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

Avoiding unnecessary try...catch

When you might encounter this: During code refactoring, when empty catch blocks containing only throw; are left behind.

If there is no processing logic inside the catch block other than throw, 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;
}

Changelog

    • Initial version created.