Skip to main content

.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:

csharp
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:

csharp
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:

csharp
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:

  1. Precision - You can be more specific about which exceptions to catch without creating custom exception types.
  2. Readability - Your code becomes more self-documenting when the filtering condition is right next to the catch block.
  3. Efficiency - Filters are evaluated before the stack is unwound, which can improve performance.
  4. 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:

csharp
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:

csharp
// 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:

csharp
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:

  1. Handle specific exception types differently
  2. Change behavior based on the environment (development vs. production)
  3. 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:

csharp
// 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:

csharp
// 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

  1. Create a method that processes a string input and uses exception filters to handle different validation errors based on the string's content.
  2. Modify an existing method that catches all exceptions to use filters to provide different handling for network exceptions versus file system exceptions.
  3. 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

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! :)