.NET Exception Filters
Exception handling is a critical part of building robust applications. When working with .NET, you have access to a powerful feature called "Exception Filters" that allows you to be more selective about which exceptions to catch. In this tutorial, we'll explore what exception filters are, how they work, and when to use them in your C# applications.
What Are Exception Filters?
Exception filters are conditions that determine whether a caught exception should be handled by a particular catch
block. They allow you to examine the exception and decide on the spot whether to handle it or let it propagate to other catch blocks or up the call stack.
In C#, exception filters are implemented using the when
keyword followed by a boolean expression. The syntax looks like this:
try
{
// Code that might throw exceptions
}
catch (ExceptionType ex) when (FilterCondition)
{
// Handle exception only if FilterCondition evaluates to true
}
Basic Exception Filter Examples
Let's start with a simple example to demonstrate how exception filters work:
public void ProcessValue(int value)
{
try
{
if (value < 0)
throw new ArgumentException("Value cannot be negative", "value");
if (value > 100)
throw new ArgumentException("Value too large", "value");
Console.WriteLine($"Processing value: {value}");
}
catch (ArgumentException ex) when (ex.Message.Contains("negative"))
{
Console.WriteLine("Caught a negative value exception");
}
catch (ArgumentException ex) when (ex.Message.Contains("too large"))
{
Console.WriteLine("Caught a large value exception");
}
catch (ArgumentException ex)
{
Console.WriteLine($"Caught another argument exception: {ex.Message}");
}
}
If we call this method with different values:
ProcessValue(-5); // Output: "Caught a negative value exception"
ProcessValue(150); // Output: "Caught a large value exception"
ProcessValue(50); // Output: "Processing value: 50"
This example shows how we can handle the same exception type differently based on specific conditions.
Benefits of Exception Filters
Exception filters offer several benefits:
- Precision - You can be more specific about which exceptions to catch without creating custom exception types.
- Readability - Your code becomes more self-documenting when the filtering condition is right next to the catch block.
- Efficiency - Filters are evaluated before the stack is unwound, which can improve performance.
- Simplification - You can avoid nested if statements inside catch blocks.
Advanced Usage with Methods in Filters
Exception filters can include method calls, making them very powerful:
public bool ShouldHandle(Exception ex)
{
// Log the exception
LogException(ex);
// Decide whether to handle based on business logic
return ex.Message.Contains("critical") || DateTime.Now.DayOfWeek == DayOfWeek.Monday;
}
public void ProcessData()
{
try
{
// Some data processing code
throw new ApplicationException("Encountered critical error in data");
}
catch (ApplicationException ex) when (ShouldHandle(ex))
{
Console.WriteLine("Handling the application exception");
// Recovery logic
}
}
In this example, the filter calls a method that both logs the exception and determines if it should be handled based on complex business rules.
Exception Filters vs. Multiple Catch Blocks
You might wonder when to use exception filters versus multiple catch blocks for different exception types. Here's a comparison:
// Using multiple catch blocks for different exception types
try
{
ProcessFile("data.txt");
}
catch (FileNotFoundException)
{
Console.WriteLine("File not found");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Access denied");
}
// Using exception filters with a single exception type
try
{
ProcessFile("data.txt");
}
catch (Exception ex) when (ex is FileNotFoundException)
{
Console.WriteLine("File not found");
}
catch (Exception ex) when (ex is UnauthorizedAccessException)
{
Console.WriteLine("Access denied");
}
Both approaches work, but:
- Use multiple catch blocks when handling completely different exception types
- Use exception filters when you need to filter based on properties or conditions of the same exception type
Real-World Example: API Error Handling
Let's see how exception filters can be applied in a real-world web API scenario:
public async Task<IActionResult> GetUserData(int userId)
{
try
{
var userData = await _userService.GetUserDataAsync(userId);
return Ok(userData);
}
catch (Exception ex) when (ex is DatabaseConnectionException)
{
_logger.LogError(ex, "Database connection failed");
return StatusCode(503, "Service temporarily unavailable");
}
catch (Exception ex) when (ex is UserNotFoundException)
{
_logger.LogWarning(ex, $"User {userId} not found");
return NotFound($"User with ID {userId} does not exist");
}
catch (Exception ex) when (_environment.IsDevelopment())
{
// In development, return detailed error info
return StatusCode(500, new { error = ex.Message, stackTrace = ex.StackTrace });
}
catch (Exception ex)
{
// In production, log error but return generic message
_logger.LogError(ex, "Unexpected error");
return StatusCode(500, "An unexpected error occurred");
}
}
This example shows how you can use exception filters to:
- Handle specific exception types differently
- Change behavior based on the environment (development vs. production)
- Provide appropriate HTTP status codes and responses
Common Pitfalls and Best Practices
When working with exception filters, keep these tips in mind:
Avoid Side Effects
While you can call methods in your filter conditions, be careful about side effects. The filter might be evaluated multiple times, or not at all if exceptions are unhandled:
// Problematic - has side effects
catch (Exception ex) when (UpdateErrorCount() > 10)
// Better - no side effects in the filter
catch (Exception ex) when (_errorCount > 10)
Filter Evaluation Order
Filters are evaluated in the order they appear. Put more specific catches first:
// Correct order
catch (ArgumentException ex) when (ex.ParamName == "username") { }
catch (ArgumentException ex) { } // General case last
// Incorrect - the second catch will never be reached
catch (ArgumentException ex) { }
catch (ArgumentException ex) when (ex.ParamName == "username") { }
Performance Considerations
Filters are executed before the stack unwinding occurs, which means they can be more efficient than catching and then checking a condition. However, overly complex filters can still impact performance.
Summary
Exception filters are a powerful feature in .NET that enhance your exception handling capabilities by allowing you to selectively catch exceptions based on specific conditions. They improve code readability, efficiency, and precision in error handling.
Key takeaways:
- Use the
when
keyword to add conditions to catch blocks - Filters enable you to handle the same exception type differently based on specific conditions
- You can call methods in filter expressions but be cautious about side effects
- Exception filters are evaluated before the stack is unwound, potentially improving performance
- Filters should be ordered from most specific to most general
Exercises
- Create a method that processes a string input and uses exception filters to handle different validation errors based on the string's content.
- Modify an existing method that catches all exceptions to use filters to provide different handling for network exceptions versus file system exceptions.
- Implement a logging system that uses exception filters to determine what level of detail to log based on the exception type and environment.
Additional Resources
- Microsoft Docs: Exception Filters
- Exception Handling Best Practices in .NET
- C# Exception Handling Fundamentals
Happy coding and may your exceptions always be properly filtered!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)