C# Exception Handling
Overview
Exception handling provides a structured way to handle runtime errors and exceptional conditions in your code.
Basic Exception Handling
Try-Catch Block
csharp
try
{
int x = 10;
int y = 0;
int result = x / y; // DivideByZeroException
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Cannot divide by zero: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"General error: {ex.Message}");
}
finally
{
Console.WriteLine("Cleanup code here");
}Multiple Catch Blocks
csharp
try
{
string input = "abc";
int number = int.Parse(input); // FormatException
}
catch (FormatException ex)
{
Console.WriteLine($"Invalid number format: {ex.Message}");
}
catch (OverflowException ex)
{
Console.WriteLine($"Number too large: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
}Custom Exceptions
csharp
public class InvalidAgeException : Exception
{
public InvalidAgeException() : base("Invalid age provided") { }
public InvalidAgeException(string message) : base(message) { }
public InvalidAgeException(string message, Exception inner)
: base(message, inner) { }
}
public class Person
{
private int _age;
public int Age
{
get => _age;
set
{
if (value < 0 || value > 150)
throw new InvalidAgeException($"Age must be between 0 and 150, got {value}");
_age = value;
}
}
}Exception Properties
csharp
try
{
// Code that throws exception
}
catch (Exception ex)
{
Console.WriteLine($"Message: {ex.Message}");
Console.WriteLine($"Source: {ex.Source}");
Console.WriteLine($"StackTrace: {ex.StackTrace}");
Console.WriteLine($"HelpLink: {ex.HelpLink}");
if (ex.InnerException != null)
{
Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
}
}Throwing Exceptions
csharp
public class BankAccount
{
private decimal _balance;
public void Withdraw(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Amount must be positive", nameof(amount));
if (amount > _balance)
throw new InvalidOperationException($"Insufficient funds. Balance: {_balance}, Requested: {amount}");
_balance -= amount;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be positive");
_balance += amount;
}
}Exception Filters (C# 6.0+)
csharp
try
{
// Code that might throw
}
catch (Exception ex) when (ex.Message.Contains("network"))
{
Console.WriteLine("Network error occurred");
}
catch (Exception ex) when (ex.Message.Contains("database"))
{
Console.WriteLine("Database error occurred");
}
catch (Exception ex)
{
Console.WriteLine($"Other error: {ex.Message}");
}Using Statement
csharp
public class FileProcessor
{
public void ProcessFile(string filePath)
{
// Using statement ensures proper disposal
using (StreamReader reader = new StreamReader(filePath))
{
string content = reader.ReadToEnd();
Console.WriteLine(content);
} // reader.Dispose() called automatically
}
// Alternative with using declaration (C# 8.0+)
public void ProcessFileModern(string filePath)
{
using var reader = new StreamReader(filePath);
string content = reader.ReadToEnd();
Console.WriteLine(content);
} // reader disposed at end of scope
}Practical Examples
Safe Division
csharp
public class SafeMath
{
public static double Divide(double numerator, double denominator)
{
try
{
return numerator / denominator;
}
catch (DivideByZeroException)
{
Console.WriteLine("Cannot divide by zero, returning 0");
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error in division: {ex.Message}");
return double.NaN;
}
}
}File Operations with Exception Handling
csharp
public class FileManager
{
public string ReadFile(string filePath)
{
try
{
if (!File.Exists(filePath))
throw new FileNotFoundException($"File not found: {filePath}");
return File.ReadAllText(filePath);
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"File not found: {ex.Message}");
return string.Empty;
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"Access denied: {ex.Message}");
return string.Empty;
}
catch (IOException ex)
{
Console.WriteLine($"IO error: {ex.Message}");
return string.Empty;
}
}
public bool WriteFile(string filePath, string content)
{
try
{
File.WriteAllText(filePath, content);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Failed to write file: {ex.Message}");
return false;
}
}
}Database Operations
csharp
public class DatabaseManager
{
public bool ExecuteQuery(string query)
{
try
{
// Simulate database operation
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentException("Query cannot be empty");
Console.WriteLine($"Executing: {query}");
return true;
}
catch (SqlException ex)
{
Console.WriteLine($"Database error: {ex.Message}");
return false;
}
catch (TimeoutException ex)
{
Console.WriteLine($"Query timeout: {ex.Message}");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
return false;
}
}
}Best Practices
Exception Handling Guidelines
csharp
// Good: Specific exception handling
try
{
int.Parse(input);
}
catch (FormatException)
{
Console.WriteLine("Invalid number format");
}
// Bad: Catching all exceptions
try
{
int.Parse(input);
}
catch (Exception) // Too general
{
Console.WriteLine("Something went wrong");
}When to Throw vs Return
csharp
public class ValidationExample
{
// Good: Throw for invalid state
public void SetAge(int age)
{
if (age < 0 || age > 150)
throw new ArgumentOutOfRangeException(nameof(age), "Age must be valid");
// Set age...
}
// Good: Return for expected failure
public bool TryParseAge(string input, out int age)
{
return int.TryParse(input, out age) && age >= 0 && age <= 150;
}
}Exception Logging
csharp
public class ExceptionLogger
{
public static void LogException(Exception ex, string context = "")
{
string logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] ";
logMessage += $"Context: {context} | ";
logMessage += $"Exception: {ex.GetType().Name} | ";
logMessage += $"Message: {ex.Message} | ";
logMessage += $"StackTrace: {ex.StackTrace}";
Console.WriteLine(logMessage);
// Could also write to file, database, etc.
}
public static T SafeExecute<T>(Func<T> operation, string context = "")
{
try
{
return operation();
}
catch (Exception ex)
{
LogException(ex, context);
return default(T);
}
}
}Summary
In this chapter, you learned:
- Basic exception handling with try-catch-finally
- Multiple catch blocks and exception hierarchy
- Custom exception classes
- Exception properties and throwing exceptions
- Exception filters and using statements
- Practical examples and best practices
Exception handling is crucial for creating robust, reliable applications that can gracefully handle errors and unexpected conditions.