A Brief Discussion on the Differences Between throw and throw ex
TLDR
- In a
catchblock, you should prioritize usingthrow;instead ofthrow 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
catchblock contains onlythrow;with no other logic, thetry...catchblock should be removed entirely. - .NET 5 and later versions have introduced the
CA2200rule, which automatically detects and warns against the use ofthrow 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.
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
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.
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:
System.DivideByZeroException: Attempted to divide by zero.
at Program.<Main>$(String[] args) in D:\Programming\Projects\TestThrow\TestThrow\Program.cs:line 5TIP
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.
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.
try {
// Code that might throw an Exception...
} catch {
// No processing done, only throw
throw;
}Changelog
- Initial version created.