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,TryGetValueinstead - Throw early, catch late — validate inputs at the boundary
- Prefer
throw;overthrow ex;to preserve the stack trace