C# Exception Filters: Quick Guide [2024]

published on 12 November 2024

Exception filters in C# are a game-changer for error handling. Here's what you need to know:

  • Introduced in C# 6.0 (2015)
  • Allow precise exception catching with the when keyword
  • Preserve stack traces for better debugging
  • Can improve performance by avoiding unnecessary stack unwinding

Key benefits:

  1. Cleaner code with less nesting
  2. More accurate error handling
  3. Faster execution in some scenarios

Basic syntax:

try
{
    // Code that might throw
}
catch (ExceptionType ex) when (condition)
{
    // Handle exception
}

Example:

catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
    Console.WriteLine("Resource not found. Check the URL.");
}

Remember: Use filters for specific error conditions, not for general exception handling.

This guide covers everything from basic usage to advanced techniques, common problems, and best practices for C# exception filters.

What Are Exception Filters

Exception filters in C# let you catch exceptions based on specific conditions. They were added in C# 6.0 back in 2015.

How to Write Exception Filters

You use the when keyword to create an exception filter:

try
{
    // Code that might throw an exception
}
catch (ExceptionType ex) when (condition)
{
    // Handle the exception if the condition is true
}

Here's a real-world example:

try
{
    // Code that might throw an ArgumentException
}
catch (ArgumentException ex) when (ex.Message.Contains("The name of the user is too short"))
{
    Console.WriteLine("User name is too short. Please try again.");
}

This catches the exception only if the message mentions a short user name.

Main Advantages

Exception filters have some key benefits:

1. Stack Traces Stay Intact

They don't mess with the stack trace. This is huge for debugging. You can see where the error started, not just where it was last caught.

"Exception filters are preferable to catching and rethrowing because they leave the stack unharmed. If the exception later causes the stack to be dumped, you can see where it originally came from, rather than just the last place it was rethrown." - Stack Overflow user

2. Cleaner Code

The when keyword makes your error handling more readable. It's easier to understand what's going on at a glance.

3. Less Nesting

You don't need as many nested if-statements in your catch blocks. This makes your code simpler.

4. Better Performance

Exception filters can be faster because they don't unwind the stack unnecessarily.

Thomas Levesque, a C# expert, says:

"Correctly understood and used, they can make it much easier to diagnose problems in your code."

Here's a practical example showing how powerful exception filters can be:

try
{
    // Code that might throw different types of exceptions
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
    Console.WriteLine("The endpoint you are trying to reach cannot be found. Please check the URL");
}
catch (CustomException ex) when (ex.HttpStatusCode == 404)
{
    // Handle CustomException exceptions with an HTTP code of 404
}
catch (Exception ex) when (ex is ArgumentException || ex is FormatException || ex is OverflowException)
{
    // Handle ArgumentException, FormatException, and OverflowException exceptions
}

This example shows how you can handle different exceptions and conditions in a clean, easy-to-read way.

Using Exception Filters in Code

Exception filters in C# let you handle errors with precision. Here's how to use them:

Basic Code Examples

The basic syntax for exception filters looks like this:

try
{
    // Code that might throw an exception
}
catch (ExceptionType ex) when (condition)
{
    // Handle the exception if the condition is true
}

Here's a real-world example:

try
{
    int result = 10 / int.Parse("0");
}
catch (DivideByZeroException ex) when (ex.Message.Contains("zero"))
{
    Console.WriteLine("You can't divide by zero!");
}

This code only catches a DivideByZeroException if the message has "zero" in it. It's a simple way to handle specific errors.

Practical Examples

Let's look at how exception filters work in real situations:

Handling HTTP Errors

When you're working with web services, you often need to deal with different HTTP status codes:

try
{
    var response = await client.GetAsync("https://api.example.com/data");
    response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
    Console.WriteLine("Can't find that resource. Double-check your URL.");
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
    Console.WriteLine("Authentication failed. Check your login info.");
}

This example shows how to handle specific HTTP status codes, which can make your error messages more helpful.

Custom Exception Handling

Exception filters are great for dealing with custom exceptions:

try
{
    ProcessConfig(configData);
}
catch (CustomException ex) when (ex.ErrorCode == 404)
{
    Console.WriteLine($"Can't find that config: {ex.Message}");
}
catch (CustomException ex) when (ex.ErrorCode == 400)
{
    Console.WriteLine($"That config isn't valid: {ex.Message}");
}

This approach lets you handle errors based on specific properties of your custom exceptions.

Multiple Exception Types

You can use exception filters to handle several exception types in one catch block:

try
{
    // Code that might throw various exceptions
}
catch (Exception ex) when (ex is ArgumentException || ex is FormatException || ex is OverflowException)
{
    Console.WriteLine($"Input error: {ex.Message}");
}

This technique can make your code cleaner by grouping related exceptions.

Exception filters are powerful, but use them wisely. Too many can make your code hard to follow. As Bill Wagner, who wrote "Effective C#", puts it:

"The exception filters in C# 6 cut back on the cumbersome error-handling code commonplace to earlier releases."

Advanced Filter Methods

Exception filters in C# let you handle tricky error situations. Let's look at some smart ways to use them.

Dealing with Multiple Exceptions

Want to handle different errors in different ways? Exception filters are your friend. Check out this real-world example from Microsoft:

try
{
    HelixApi = GetHelixApi();
    AnonymousApi = ApiFactory.GetAnonymous(BaseUri);
    System.Threading.Tasks.Task.Run(() => ExecuteCore(_cancel.Token)).GetAwaiter().GetResult();
}
catch (RestApiException ex) when (ex.Response.Status == (int)HttpStatusCode.Unauthorized)
{
    Log.LogError(FailureCategory.Build, "Helix operation returned 'Unauthorized'. Did you forget to set HelixAccessToken?");
}
catch (RestApiException ex) when (ex.Response.Status == (int)HttpStatusCode.Forbidden)
{
    Log.LogError(FailureCategory.Build, "Helix operation returned 'Forbidden'.");
}
catch (OperationCanceledException ocex) when (ocex.CancellationToken == _cancel.Token)
{
    return false;
}
catch (ArgumentException argEx) when (argEx.Message.StartsWith("Helix API does not contain an entry "))
{
    if (FailOnMissingTargetQueue)
    {
        Log.LogError(FailureCategory.Build, argEx.Message);
    }
    else
    {
        Log.LogWarning($"{argEx.Message} (FailOnMissingTargetQueue is false, so this is just a warning.)");
    }
}
catch (Exception ex)
{
    Log.LogErrorFromException(FailureCategory.Helix, ex, true, true, null);
}

This code handles different HTTP status codes, cancellations, and custom error messages. It's like having a different plan for each type of problem.

Creating Your Own Exceptions

Sometimes, you need exceptions that fit your specific app. Here's how to make and use one:

public class InvalidNameException : Exception
{
    public InvalidNameException() { }
    public InvalidNameException(string message) : base(message) { }
    public InvalidNameException(string message, Exception inner) : base(message, inner) { }
}

public void ValidateName(string name)
{
    if (string.IsNullOrWhiteSpace(name))
        throw new InvalidNameException("Name cannot be empty or null.");
}

try
{
    ValidateName(null);
}
catch (InvalidNameException ex) when (ex.Message.Contains("empty or null"))
{
    Console.WriteLine("Please provide a valid name.");
}

This code creates a special InvalidNameException. The filter checks if the error message talks about an empty or null name. It's like having a custom alarm for a specific problem.

You can even add extra info to your custom exceptions:

public class InvalidAgeException : Exception
{
    public int Age { get; }
    public InvalidAgeException(int age)
    {
        Age = age;
        Data.Add("Age", Age);
    }
}

try
{
    throw new InvalidAgeException(15);
}
catch (InvalidAgeException ex) when (ex.Age < 18)
{
    Console.WriteLine($"User is underage: {ex.Age} years old");
}

This example shows how to include the age in the exception. It's like attaching a note to your error message with extra details.

sbb-itb-29cd4f6

Filters vs. Old Methods

Exception filters in C# 6.0 changed the game for error handling. They're a step up from the old catch-and-rethrow methods. Let's break down why they're often the smarter choice.

What's New?

Filters shine in three main areas: stack traces, code readability, and speed.

Stack Traces

Old methods often messed up stack traces, making bug-hunting a pain. Filters? They keep the full trace intact. Here's how it looks:

// Old way
try
{
    // Code that might break
}
catch (Exception ex)
{
    if (ex.Message.Contains("specific error"))
    {
        // Handle it
    }
    else
    {
        throw; // Keeps stack trace
        // or
        throw ex; // Resets stack trace
    }
}

// New way with filters
try
{
    // Code that might break
}
catch (Exception ex) when (ex.Message.Contains("specific error"))
{
    // Handle it
}

The old way? You had to choose between keeping or resetting the stack trace. Filters? They just work, no extra steps needed.

Cleaner Code

Filters make your code easier to read and manage. No more nested ifs in catch blocks. Just use when to set your conditions.

Mike Magruder from CLR has some advice:

"The key is to only read information from either the exception object itself, or from immutable global state, and to not change any global state."

Stick to this, and your filters will play nice with the rest of your code.

Speed Boost

Filters can be faster than old methods. Here's why: In the old way, the CLR always unwinds the stack when it hits a catch block. That's slow.

With filters, it checks the condition first. If it doesn't match, it skips the catch block entirely. That can save a lot of processing time.

Let's compare:

// Old way (slower)
static void Method_PlainCatchBlock() 
{
    try 
    {
        MethodThrowing();
    } 
    catch (Exception e) 
    {
        if (e.Message == "Specific Error") 
        {
            Console.WriteLine("Handled Specific Error");
        } 
        else 
        {
            throw;
        }
    }
}

// New way with filter (faster)
static void Method_Filter() 
{
    try 
    {
        MethodThrowing();
    } 
    catch(Exception e) when (e.Message == "Specific Error") 
    {
        Console.WriteLine("Handled Specific Error");
    }
}

In tests, the filter version consistently beats the old method, especially when exceptions are common but you only need to handle specific ones.

Bill Wagner, who wrote "Effective C#", puts it well:

"The exception filters in C# 6 cut back on the cumbersome error-handling code commonplace to earlier releases."

So, not only is your code cleaner, it's also more efficient. That's a win-win.

Common Problems and Solutions

Let's dive into some issues developers often face with exception filters in C# and how to fix them.

Debugger Stopping at Custom Exceptions

Ever had your debugger stop at custom exceptions when you're just trying to validate input? It's a real pain.

Here's how to fix it:

Double-check your exception filter settings. Believe it or not, a simple typo in the namespace can cause this. As one developer put it:

"In the end I was very close with my PS. It was a pretty silly mistake: I had a typo in the namespace."

So, make sure your namespace is spot-on in those exception filter settings.

Unhandled Exceptions in Filters

Here's a tricky one: what happens when your filter throws an exception? Your program might just crash. Take a look at this code:

try {
    throw new Exception("Forced Exception");
}
catch (Exception ex) when (MethodThatThrowsAnException()) {
    Console.WriteLine("Filtered handler 1");
}

private bool MethodThatThrowsAnException() {
    throw new Exception("MethodThatThrowsAnException");
}

Run this, and you'll get:

Unhandled Exception: System.Exception: MethodThatThrowsAnException

The fix? Make sure your filter conditions are rock-solid. If there's a chance they might throw an exception, handle it in the filter or use a simpler condition.

Performance Concerns

Exception filters are cool, but they can slow things down if you're not careful. Exceptions are WAY slower than normal code.

So, what should you do? Only use try-catch for REAL exceptions. For everyday stuff like parsing integers, there are faster ways. For example, Int32.TryParse is thousands of times quicker than a try-catch.

As Mohammad Mobasher says:

"Always bear in mind that exceptions are considerably slower than normal code flow."

Testing and Speed Tips

Want to find and fix exception filter problems fast? Here's how:

  1. Use Visual Studio's debugging features. Turn on first chance exceptions to catch all exceptions as they happen.
  2. Get to know the Exception Settings Window. Michael Shpilt, a coding expert, says:

    "The exception settings window is one of the most important debugging windows. You should always be aware whether you are currently breaking on exceptions or not."

  3. When you're dealing with "exceptions" that you expect, look for alternatives to exception handling. Like using TryParse instead of catching FormatExceptions for input checks.
  4. Use exception filters to keep the original context of an exception. This helps you debug and figure out what was going on when things went wrong.
  5. Keep your filter conditions simple and clear. It makes your code easier to read and less likely to have bugs in the filters themselves.

Conclusion

Exception filters in C# have changed the game for error handling since C# 6.0 in 2015. They're a big deal for developers, making exception management more precise and efficient.

Here's why exception filters are so important:

  1. They keep the stack trace intact. As one Stack Overflow user put it:

"Exception filters are preferable to catching and rethrowing because they leave the stack unharmed. If the exception later causes the stack to be dumped, you can see where it originally came from, rather than just the last place it was rethrown."

This is huge for debugging and figuring out what went wrong.

  1. They make your code cleaner and easier to read. The when keyword lets you set specific conditions for catching exceptions, so you don't need as many nested if-statements.
  2. They're faster. Unlike regular catch blocks, exception filters don't mess with the stack unnecessarily. This can speed things up, especially when exceptions happen a lot.
  3. They're flexible. You can use them with both built-in and custom exceptions, and handle multiple exception types in one catch block.
  4. They come with best practices. Mike Magruder from CLR says:

"The key is to only read information from either the exception object itself, or from immutable global state, and to not change any global state."

This keeps your exception handling predictable and avoids side effects.

Bill Wagner, who wrote "Effective C#: 50 Specific Ways to Improve Your C#, Second Edition", sums it up nicely:

"The exception filters in C# 6 cut back on the cumbersome error-handling code commonplace to earlier releases."

Less boilerplate code means cleaner code and fewer chances for errors in your exception handling.

Exception filters show how Microsoft is working to make developers more productive and improve code quality. If you want to stay in the loop on C# and .NET, check out the .NET Newsletter (https://dotnetnews.co). It gives you daily updates on C#, ASP.NET, Azure, and related tech from various sources.

FAQs

How to filter exceptions in C#?

In C#, you can filter exceptions using the when keyword in your catch clause. Here's the basic syntax:

catch (ExceptionType ex) when (boolean expression)

Let's look at a real-world example:

try
{
    DatabaseUpdate();
}
catch (SQLException e) when (e.Message.Contains("MySQL"))
{
    Console.WriteLine("MySQL error: Oops!");
}
catch (SQLException e) when (e.Message.Contains("Oracle"))
{
    Console.WriteLine("Oracle error: Yikes!");
}

This approach lets you handle different scenarios for the same exception type. It's like having a bouncer at a club who only lets in certain types of errors!

What are exception filters in C#?

Exception filters are like bouncers for your catch blocks. They decide which errors get VIP treatment. Introduced in C# 6.0, these filters let you add conditions to your exception handling.

Here's what you need to know:

  1. They use the when keyword followed by a boolean expression.
  2. They don't mess with the stack, keeping the original error info intact.
  3. They can make your code run faster by avoiding unnecessary error handling.

As software engineer Eli Arbel puts it:

"Exception filters are preferable to catching and rethrowing because they leave the stack unharmed."

Think of exception filters as a way to write smarter, more efficient error-handling code. No more spaghetti-like nested if-statements in your catch blocks!

Related posts

Read more