C# Exception Handling Explained: Handle Errors Safely
Exception handling in C# provides a structured way to detect and respond to unexpected situations during program execution. Using try, catch, finally, and throw correctly helps applications recover from errors and remain reliable.
What is an Exception?
An exception is an object that represents an error or unusual condition that occurs while a program is running. Examples include invalid input, missing files, unavailable resources, and invalid operations.
int number = int.Parse("abc");
// FormatException occurs because "abc" is not a valid integer.
Why Use Exception Handling?
- Prevent unexpected application crashes
- Provide meaningful error messages
- Recover from expected runtime problems
- Clean up resources safely
- Separate normal logic from error-handling logic
- Create more reliable applications
The try-catch Block
The try block contains code that may throw an exception. The catch block handles an exception when one occurs.
try
{
int number = int.Parse("abc");
Console.WriteLine(number);
}
catch (FormatException)
{
Console.WriteLine("Invalid number format.");
}
Handling Different Exception Types
A program can use multiple catch blocks to handle different exception types separately.
try
{
int[] numbers = { 10, 20, 30 };
Console.WriteLine(numbers[10]);
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Invalid array index.");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
}
The finally Block
The finally block runs after the try and catch processing. It is commonly used for cleanup operations that should happen regardless of whether an exception occurred.
try
{
Console.WriteLine("Processing data...");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("Cleanup completed.");
}
Using throw
The throw statement allows an application to explicitly raise an exception when an invalid condition is detected.
static void ValidateAge(int age)
{
if (age < 18)
{
throw new ArgumentException("Age must be at least 18.");
}
Console.WriteLine("Age is valid.");
}
Rethrowing Exceptions
An exception can be caught, logged or inspected, and then rethrown using throw. Using throw without specifying the exception preserves the original stack trace.
try
{
ProcessData();
}
catch (Exception)
{
Console.WriteLine("Logging error...");
throw;
}
Common Built-In Exceptions
| Exception | Typical Cause | Example |
|---|---|---|
| ArgumentException | Invalid argument | Invalid method parameter |
| ArgumentNullException | Null argument | Required parameter is null |
| FormatException | Invalid data format | Parsing invalid text |
| InvalidOperationException | Invalid operation state | Operation cannot run in current state |
| FileNotFoundException | Missing file | Opening a nonexistent file |
| IndexOutOfRangeException | Invalid index | Accessing outside an array |
Custom Exceptions
Custom exceptions can represent application-specific error conditions. They are useful when an error needs a clear domain-specific meaning.
class InsufficientBalanceException : Exception
{
public InsufficientBalanceException(string message)
: base(message)
{
}
}
static void Withdraw(decimal balance, decimal amount)
{
if (amount > balance)
{
throw new InsufficientBalanceException(
"Insufficient account balance.");
}
}
Catching a Custom Exception
try
{
Withdraw(500, 1000);
}
catch (InsufficientBalanceException ex)
{
Console.WriteLine(ex.Message);
}
Exception Filters
Exception filters allow a catch block to run only when an additional condition is satisfied.
try
{
ProcessRequest();
}
catch (Exception ex) when (ex.Message.Contains("network"))
{
Console.WriteLine("Network-related error.");
}
Exception Handling with File Operations
File operations can fail for several reasons, such as missing files or insufficient permissions. Exceptions can be handled at an appropriate application boundary.
try
{
string content = File.ReadAllText("data.txt");
Console.WriteLine(content);
}
catch (FileNotFoundException)
{
Console.WriteLine("The file does not exist.");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Access to the file was denied.");
}
Exception Handling in Methods
A method does not always need to catch every exception itself. Sometimes it is better to allow an exception to move to a higher-level component that can make the appropriate decision.
static string LoadConfiguration()
{
return File.ReadAllText("config.json");
}
try
{
string config = LoadConfiguration();
}
catch (FileNotFoundException)
{
Console.WriteLine("Configuration file is missing.");
}
Exception Handling vs Validation
Not every invalid input should be handled through exceptions. Expected user input validation is often better handled with normal conditional logic before performing an operation.
string input = "123";
if (int.TryParse(input, out int number))
{
Console.WriteLine($"Number: {number}");
}
else
{
Console.WriteLine("Please enter a valid number.");
}
Exception Handling Best Practices
- Catch only exceptions you can meaningfully handle
- Use specific exception types instead of catching everything unnecessarily
- Preserve stack traces when rethrowing with throw
- Use finally or using for cleanup when appropriate
- Do not expose sensitive internal error details to users
- Log unexpected failures at suitable application boundaries
- Validate expected input without using exceptions as normal control flow
Common Mistakes to Avoid
- Using empty catch blocks
- Catching Exception everywhere
- Ignoring exceptions without logging or handling them
- Throwing overly generic exceptions
- Using exceptions for ordinary validation
- Losing the original stack trace when rethrowing
- Displaying technical exception details directly to end users
Exception Handling Comparison
| Technique | Purpose | Typical Usage |
|---|---|---|
| try-catch | Handle an exception | Recover or report an error |
| finally | Perform cleanup | Release resources |
| throw | Raise an exception | Report invalid conditions |
| Exception filter | Conditionally catch | Handle specific situations |
| Custom exception | Represent domain-specific errors | Business/application rules |
Real-World Applications
- API error handling
- File processing
- Database operations
- Payment processing
- User input validation
- Authentication failures
- External service communication
Practice Exercises
- Create a try-catch block for invalid number input
- Handle a missing file exception
- Create a custom InsufficientBalanceException
- Use finally for cleanup
- Practice multiple catch blocks
- Implement an exception filter
- Build a small application-level error handler
Conclusion
Effective exception handling helps C# applications deal with unexpected failures in a controlled way. By using specific exceptions, meaningful recovery logic, proper cleanup, and clear validation strategies, developers can create applications that are safer and easier to maintain.