MSDN Documentation

.NET Fundamentals: Exception Handling

Introduction to Exception Handling in .NET

Robust applications must gracefully handle unexpected events that occur during runtime. In the .NET framework, this is primarily achieved through a structured exception handling mechanism. Exceptions are runtime errors that disrupt the normal flow of an application's instructions. By using exception handling, you can catch these errors, log them, and potentially recover or inform the user in a controlled manner, preventing abrupt application termination.

The .NET Exception Hierarchy

All exceptions in .NET inherit from the System.Exception class. This forms a hierarchy that allows for flexible handling of different types of errors. Key exceptions include:

Understanding the exception hierarchy is crucial for writing effective exception handling code. Catching a base class exception will also catch all derived exceptions.

The try-catch-finally Block

The fundamental structure for handling exceptions in C# is the try-catch-finally statement.

Here's a basic example:


try
{
    // Code that might throw an exception
    int result = Divide(10, 0);
    Console.WriteLine($"Result: {result}");
}
catch (DivideByZeroException ex)
{
    // Handle the specific exception
    Console.WriteLine($"Error: Cannot divide by zero. {ex.Message}");
}
catch (Exception ex)
{
    // Handle any other exceptions
    Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}
finally
{
    // Cleanup code that always runs
    Console.WriteLine("This finally block always executes.");
}

public int Divide(int numerator, int denominator)
{
    if (denominator == 0)
    {
        throw new DivideByZeroException("Denominator cannot be zero.");
    }
    return numerator / denominator;
}
            

Catching Specific Exceptions

It's best practice to catch the most specific exception types first. This allows you to handle different error conditions appropriately.

Catching General Exceptions

A general catch (Exception ex) block should typically be the last one to catch any exceptions not handled by more specific catch blocks. Avoid catching System.Exception and doing nothing with it (swallowing the exception), as this can hide critical errors.

Always log exceptions, especially those caught by a general catch block, to aid in debugging.

Exception Filters

Exception filters, introduced in C# 6, provide a way to specify a condition within a catch clause. The catch block will only be entered if the condition evaluates to true. This is useful for logging or re-throwing an exception under certain circumstances without executing the catch block's full logic.


try
{
    // ... code ...
}
catch (IOException ex) when (ex.FileName != null)
{
    // Handle IOExceptions only if a file name is present
    Console.WriteLine($"IO Error with file: {ex.FileName}");
}
            

Throwing Exceptions

You can manually throw exceptions using the throw keyword. This is done when an operation cannot be performed due to invalid input or state.


public void SetValue(int value)
{
    if (value < 0)
    {
        throw new ArgumentOutOfRangeException(nameof(value), "Value cannot be negative.");
    }
    // ... set value ...
}
            

When throwing an exception, always provide a descriptive message explaining the cause of the error. Consider creating custom exception types for application-specific errors if the built-in exceptions are not sufficient.

Exception Safety and Resource Management

When dealing with unmanaged resources (like file handles or network connections), it's critical to ensure they are released, even if exceptions occur. The finally block is one way, but the using statement provides a more concise and robust solution for objects implementing IDisposable.


try
{
    using (StreamReader reader = new StreamReader("myFile.txt"))
    {
        string line = reader.ReadLine();
        // Process the line
    } // reader.Dispose() is automatically called here, even if an exception occurs
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"File not found: {ex.Message}");
}
            
Failure to release resources can lead to memory leaks and system instability.

Best Practices for Exception Handling