throw; and throw ex; both compile inside a catch block. They do not produce the same stack trace.
This version resets the stack trace at the rethrow location:
try
{
await shippingClient.CreateShipmentAsync(order, cancellationToken);
}
catch (HttpRequestException ex)
{
logger.LogError(ex, "Could not create shipment for {OrderId}", order.Id);
throw ex;
}
The log written inside the catch still sees the exception before it is rethrown. Code further up the call chain does not get the same diagnostic detail. throw ex; rethrows the exception from that statement, so callers see a stack trace reset to this method. The original throw location and the intervening frames are no longer visible.
Rethrow the exception without naming it:
try
{
await shippingClient.CreateShipmentAsync(order, cancellationToken);
}
catch (HttpRequestException ex)
{
logger.LogError(ex, "Could not create shipment for {OrderId}", order.Id);
throw;
}
The bare throw; rethrows the exception currently handled by that catch block and preserves its original stack trace. The .NET analyzer rule CA2200 flags the throw ex; form for this reason.
A catch whose only statement is throw; adds nothing. Remove that handler and let the exception travel up the call chain on its own. Keep the handler when it has a job, such as translating a known failure or recording useful local context when that layer owns the log. Logging and rethrowing at several layers usually creates duplicate error events.
If you translate the failure into a different exception type, pass the original exception as the inner exception:
catch (SqlException ex)
{
throw new OrderPersistenceException(
$"Could not save order {order.Id}.",
ex);
}
That is a new exception with its own stack trace. The original failure remains available through InnerException.
A bare throw; works only inside the catch block that handles the exception. If the same exception must be rethrown outside its active catch block, capture it with ExceptionDispatchInfo.Capture(ex) inside the handler and call Throw() on the captured value at the later handoff point.
For the normal case, the rule is short: when a catch needs to pass the same exception upward, use throw;.