Exception Handling

Exceptions represent runtime errors. C# uses try/catch/finally to handle them gracefully instead of crashing.

Basic Pattern

try
{
    int result = int.Parse("not a number");
}
catch (FormatException ex)
{
    Console.WriteLine($"Parse error: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"Unexpected: {ex.Message}");
}
finally
{
    Console.WriteLine("Always runs (cleanup)");
}

Catch Specific Exceptions First

Catch from most specific to most general:

try
{
    var data = File.ReadAllText("missing.txt");
}
catch (FileNotFoundException)
{
    Console.WriteLine("File does not exist");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("No permission to read file");
}
catch (IOException ex)
{
    Console.WriteLine($"I/O error: {ex.Message}");
}

Custom Exceptions

public class InsufficientFundsException : Exception
{
    public decimal Amount { get; }
    public decimal Balance { get; }

    public InsufficientFundsException(decimal amount, decimal balance)
        : base($"Cannot withdraw {amount:C}. Balance: {balance:C}")
    {
        Amount = amount;
        Balance = balance;
    }
}

// Usage
public void Withdraw(decimal amount)
{
    if (amount > Balance)
        throw new InsufficientFundsException(amount, Balance);
    Balance -= amount;
}

IDisposable and using

For resources that need cleanup (files, connections, streams), use using:

// using statement — Dispose() called automatically
using var reader = new StreamReader("file.txt");
string content = reader.ReadToEnd();
// reader.Dispose() called here, even if an exception occurs

// Equivalent to:
StreamReader reader2 = new("file.txt");
try
{
    string text = reader2.ReadToEnd();
}
finally
{
    reader2.Dispose();
}

Best Practices

  • Don't catch Exception broadly unless you rethrow — it hides bugs
  • Don't use exceptions for control flow — use TryParse, TryGetValue instead
  • Throw early, catch late — validate inputs at the boundary
  • Prefer throw; over throw ex; to preserve the stack trace