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.
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.
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 3Problems 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.
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.
System.DivideByZeroException: Attempted to divide by zero.
at Program.<Main>$(String[] args) in D:\Programming\Projects\TestThrow\TestThrow\Program.cs:line 5TIP
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.
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.
try {
// Code that might throw an Exception...
} catch {
// No processing done, only throw
throw;
}Change Log
- 2025-07-31 Initial document creation.