Lesson 20 / 25

Exceptions

Throw, catch and design exceptions without hiding bugs.

Exceptions are for exceptional cases

An exception signals that a method cannot do what its name promises. Throw built-in types where they fit (ArgumentException, ArgumentNullException, InvalidOperationException, KeyNotFoundException), and the guard helpers such as ArgumentNullException.ThrowIfNull(x) keep checks short. Catch an exception only where you can handle it: retry, fall back, translate it into a meaningful error, or log it at a boundary. Catch specific types before general ones, and use exception filters (catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)) to catch conditionally. To rethrow, write throw;, which preserves the original stack trace; throw ex; resets it and hides where the error started. finally runs whether or not an exception occurred. Do not use exceptions for normal control flow: expected outcomes such as invalid user input are better served by TryParse-style methods or result types.

Good and bad exception handling

Only catch what you can handle; let the rest reach a global handler.

async Task<Product?> LoadProductAsync(string id, CancellationToken ct)
{
    ArgumentException.ThrowIfNullOrWhiteSpace(id);
    try
    {
        return await _api.GetProductAsync(id, ct);
    }
    catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
    {
        return null;                       // an expected case, handled
    }
    catch (HttpRequestException ex)
    {
        _logger.LogWarning(ex, "Catalogue call failed for {ProductId}", id);
        throw;                             // keep the original stack trace
    }
}

// avoid:
// try { ... } catch (Exception) { }      // swallows every bug silently

A fire alarm, not a doorbell

Exceptions are fire alarms: loud, expensive and reserved for real emergencies. Using them to announce visitors (expected situations) means people stop paying attention, and the cost adds up.

Quick check: Why prefer `throw;` over `throw ex;` when rethrowing?

  • throw; is faster to type
  • throw ex; does not compile
  • throw; converts the exception to a warning
  • throw; preserves the original stack trace
Answer

throw; preserves the original stack trace — throw ex; resets the stack trace to the current location, hiding the real origin.