.NETMedium
throw vs throw ex in C# — the stack-trace difference with example
Short answer: throw preserves the original stack trace. throw ex overwrites it, losing the line where the exception was originally raised.
Demo
void Inner() => throw new InvalidOperationException("boom");
void OuterRethrow()
{
try { Inner(); }
catch (Exception ex) { throw; } // ✅ stack trace points to Inner()
}
void OuterReset()
{
try { Inner(); }
catch (Exception ex) { throw ex; } // ❌ stack trace now points to OuterReset()
}
Why this is a production-critical bug
In a microservice, throw ex makes every error look like it came from the catch block. You lose the database call, the HTTP call, the validation method — anywhere the real failure lived. Debugging then requires re-running the failure with a debugger attached.
Three correct rethrow patterns
| Pattern | When |
|---|---|
throw; | You want to bubble unchanged |
throw new MyException("context", ex); | You want to add semantic meaning; original goes in InnerException |
ExceptionDispatchInfo.Capture(ex).Throw(); | You stored the exception and want to rethrow later, preserving the trace |
Roslyn analyzer
Enable CA2200: Rethrow to preserve stack details in .editorconfig to fail builds that contain throw ex.
dotnet_diagnostic.CA2200.severity = error
Real incident pattern
A team I worked with had throw ex in a global exception filter. Every 5xx looked identical in App Insights — same stack, same line. Removing it surfaced four distinct root causes that had been hiding for months.