Exception Filtering in C# Using the When Keyword

Exception filtering in C# allows developers to catch exceptions conditionally by pairing the when keyword with a boolean expression directly in the catch clause. Introduced in C# 6.0, this feature determines whether a specific catch block should execute without unwinding the call stack unless the condition evaluates to true. This article explains how exception filters work, their core benefits over traditional conditional logic within catch blocks, and practical examples of their implementation.

Syntax and Basic Usage

An exception filter appends the when keyword followed by a boolean condition to a standard catch statement. The runtime evaluates this expression before entering the catch block.

try
{
    // Code that may throw an exception
    await httpClient.GetStringAsync("https://api.example.com/data");
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
    Console.WriteLine("The requested resource was not found.");
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
    Console.WriteLine("Access is unauthorized.");
}

If the condition inside the when clause evaluates to true, the corresponding catch block executes. If it evaluates to false, the runtime ignores that block and continues searching down the chain for another matching handler or lets the exception propagate up the call stack.

Stack Trace Preservation

Before exception filters, conditional handling required catching the exception and evaluating an if condition inside the block. If the condition was not met, developers had to rethrow the exception using throw;.

// The old approach (can obscure debugging)
catch (SqlException ex)
{
    if (ex.Number == 1205) // Deadlock
    {
        HandleDeadlock(ex);
    }
    else
    {
        throw; // Unwinds and modifies stack state during traversal
    }
}

Using throw; unwinds the call stack to the point of the catch block before propagating. In contrast, exception filters evaluate the condition before the stack unwinds. If the filter condition returns false, the stack remains completely intact, preserving the original state of local variables and frame history for crash dumps and diagnostic tools.

Cleaner Multi-Condition Handling

Exception filters eliminate the need for nested conditional logic and duplicate catch structures. You can define distinct recovery strategies for different states of the same exception type.

catch (Win32Exception ex) when (ex.NativeErrorCode == 0x00000005)
{
    // Specific handling for Access Denied
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 0x00000020)
{
    // Specific handling for Sharing Violation
}
catch (Win32Exception ex)
{
    // General fallback for all other Win32 errors
}

The runtime evaluates handlers in sequential order from top to bottom. It executes the first handler whose exception type matches and whose filter evaluates to true.

Non-Intrusive Logging

Because a filter expression can execute any method returning a boolean, you can use it to log exceptions without actually handling them. By returning false from a logging method, the exception is recorded while still allowing it to bubble up to higher-level handlers.

catch (Exception ex) when (LogException(ex))
{
    // This block is never entered if LogException returns false.
}

private static bool LogException(Exception ex)
{
    Logger.LogError(ex, "An unhandled exception occurred.");
    return false; // Tells the runtime to keep looking for other handlers
}

Best Practices

  • Avoid Side Effects: Filter conditions should execute quickly and avoid mutating application state, as they run during the exception search phase.
  • Avoid Throwing from Filters: If an exception is thrown inside a when clause, the runtime ignores the secondary exception and treats the filter result as false.
  • Order Specifically: Always place more specific filter conditions above general ones to prevent broader clauses from intercepting the exception first.