# Exception Handling

## 1. try/catch/finally

```csharp
try
{
    int[] arr = { 1, 2, 3 };
    Console.WriteLine(arr[10]); // throws IndexOutOfRangeException
}
catch (IndexOutOfRangeException e)
{
    Console.WriteLine($"Caught: {e.Message}");
}
finally
{
    Console.WriteLine("Always runs — cleanup goes here");
}
```

`finally` runs whether the `try` block completes normally, throws and gets caught, or throws and is NOT caught (it runs on the way out before the exception propagates further). It's the right place for cleanup that must happen regardless of outcome — though `using`/`using var` (see Common Pitfalls) is usually a better fit for disposable resources specifically.

## 2. Catching Multiple Exception Types

```csharp
try
{
    ProcessOrder(orderId);
}
catch (InvalidOperationException e)
{
    Console.WriteLine($"Business rule violated: {e.Message}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Bad input: {e.Message}");
}
catch (Exception e)
{
    Console.WriteLine($"Unexpected: {e.Message}");
    throw; // re-throw, preserving the original stack trace
}
```

C# has no multi-catch syntax like Java's `catch (A | B e)` — instead, list separate `catch` clauses, most specific first. The runtime tries them top-to-bottom and enters the first one whose type matches (including matching a subtype). A bare `catch (Exception e)` at the end acts as a catch-all; ending it with `throw;` (not `throw e;`) re-raises the exact original exception with its original stack trace intact — `throw e;` instead resets the stack trace to this rethrow point, discarding where it actually originated.

## 3. The Exception Hierarchy — No Checked Exceptions

```csharp
// System.Exception
//   └── System.SystemException
//         ├── ArgumentException
//         │     └── ArgumentNullException
//         ├── InvalidOperationException
//         ├── IndexOutOfRangeException
//         └── NullReferenceException
//   └── ApplicationException (legacy, rarely used for custom exceptions today)
```

Every exception in .NET ultimately derives from `System.Exception`. Unlike Java, **C# has no "checked" exceptions** — nothing in the method signature declares what a method might throw, and the compiler never forces a caller to catch or declare anything. Every exception in C# behaves like Java's unchecked (`RuntimeException`) exceptions: any method can throw any exception at any time with zero compile-time obligation on callers. This is a deliberate language design choice — C#'s designers considered Java's checked-exception requirement to encourage swallowing exceptions with empty catch blocks just to satisfy the compiler, so C# dropped the mechanism entirely and relies on documentation/convention (and XML doc `<exception>` tags) to communicate what a method might throw.

## 4. Custom Exceptions

```csharp
class InsufficientFundsException : Exception
{
    public decimal Requested { get; }
    public decimal Available { get; }

    public InsufficientFundsException(decimal requested, decimal available)
        : base($"requested {requested}, only {available} available")
    {
        Requested = requested;
        Available = available;
    }
}

static decimal Withdraw(decimal balance, decimal amount)
{
    if (amount > balance) throw new InsufficientFundsException(amount, balance);
    return balance - amount;
}
```

A custom exception is an ordinary class deriving from `Exception` (or a more specific built-in exception when that's semantically accurate). Calling the base constructor with a message populates `.Message`; any extra domain data (like `Requested`/`Available` here) is just plain properties set in the constructor, readable by any catch block that catches this specific type.

## 5. Exception Filters — `catch (Exception e) when (condition)`

```csharp
try
{
    CallExternalApi();
}
catch (HttpRequestException e) when (e.StatusCode == HttpStatusCode.TooManyRequests)
{
    Console.WriteLine("Rate limited — back off and retry");
}
catch (HttpRequestException e) when (e.StatusCode >= HttpStatusCode.InternalServerError)
{
    Console.WriteLine("Server error — safe to retry");
}
catch (HttpRequestException e)
{
    Console.WriteLine($"Client error, not retrying: {e.StatusCode}");
}
```

The `when` clause is a C#-specific feature with no Java equivalent: the runtime only enters a `catch` block if BOTH the type matches AND the `when` predicate evaluates true — otherwise it keeps searching subsequent `catch` clauses (or propagates further if none match), rather than entering the block and needing an `if`/re-throw inside it. This also means a filter that returns `false` never unwinds the stack past that point, which matters for debugging: the original throw site's full state is still inspectable if a debugger is attached, unlike code that catches broadly and re-throws based on an internal `if`.

## Under the Hood: Stack Unwinding and Why `throw;` Preserves the Trace

When an exception is thrown, the CLR walks up the call stack frame by frame looking for a matching `catch` block (matching by type, and now also by any `when` filter — filters are actually evaluated *before* the stack unwinds past that frame, which is what lets a debugger still see the original throw-site state even for a filter that ultimately returns false). Each exception object carries a `StackTrace` property that the runtime populates incrementally as it's thrown; `throw;` inside a catch block re-raises the *same* exception object without touching that property, while `throw e;` is treated as throwing a *new* throw event from that location, resetting `StackTrace` to start from the rethrow point and losing the original failure location — a very common debugging pitfall traced directly to this mechanism.

![The CLR first searches up the stack for a matching catch, running when-filters without unwinding, then unwinds frames running finally blocks before entering the catch](diagrams/01-stack-unwinding.svg)

## Common Pitfalls

1. **Using `throw e;` instead of `throw;`** inside a catch block — silently discards the original stack trace, making the real failure site invisible in logs.
2. **Catching `Exception` broadly and swallowing it** (empty catch block, or just logging and continuing) — since C# has no checked exceptions forcing acknowledgment, this is easy to do accidentally and it hides real bugs; catch the most specific type you can actually handle meaningfully.
3. **Using exceptions for ordinary control flow** (e.g. parsing a string and catching `FormatException` instead of using `int.TryParse`) — exceptions are relatively expensive (stack trace capture, unwinding) and `Try*`-pattern methods exist in the BCL specifically to avoid this.
4. **Manually calling `.Dispose()` in a `finally` block instead of `using`** — verbose and easy to get wrong (forgetting a branch, or disposing twice); `using`/`using var` compiles to the exact same try/finally pattern automatically and can't be forgotten.
5. **Assuming `catch (ArgumentException e) when (someCondition)` skips evaluating `someCondition` if the type doesn't match** — filters are only evaluated once the type already matches, so a filter never runs against an exception of the wrong type; but a filter that itself throws will propagate that new exception, silently masking the original.

## Quick Reference

```
try { ... } catch (SpecificEx e) { ... } catch (Exception e) { ... } finally { ... }
throw;              → re-throw, preserves original stack trace
throw e;             → re-throw, RESETS stack trace to here (usually wrong)
catch (T e) when (cond)  → only enters if type matches AND cond is true
class MyEx : Exception { public MyEx(...) : base(message) { ... } }
No checked exceptions — nothing is enforced by the compiler, ever
using var x = ...;     → deterministic Dispose(), even on exception (finally-equivalent)
```
