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
whenclause, the runtime ignores the secondary exception and treats the filter result asfalse. - Order Specifically: Always place more specific filter conditions above general ones to prevent broader clauses from intercepting the exception first.