Back to Blog
C#

C# catch when: Using Exception Filters in C#

c# catch when: Learn how to use the C# catch when clause to filter exceptions with precise conditions, improving error handling clarity and control.

C#exception handlingexception filterserror handlingC# syntax
Illustration of a C# exception filter using catch when to conditionally handle exceptions.

c# catch when requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The catch when clause in C# lets you attach a filter expression to a catch block. The filter runs before the catch block is entered. If the filter returns true, the catch block executes. If it returns false, the exception continues to propagate and other catch blocks or the caller can handle it. This is useful when you want to handle an exception only under specific conditions without catching it unconditionally.

The Syntax of catch when

The syntax is straightforward:

try { // code that may throw } catch (Exception ex) when (condition) { // handle exception }

The condition can be any Boolean expression. It can reference the exception variable ex, variables from the surrounding scope, or even call methods. The filter is evaluated when an exception is thrown, before any catch block is entered.

How Exception Filters Differ from Catch Blocks

A traditional catch block catches all exceptions of the specified type. To conditionally handle them, you often write:

try { // ... } catch (InvalidOperationException ex) { if (ex.Message.Contains("specific")) { // handle } else { throw; } }

With catch when, you move the condition into the filter:

try { // ... } catch (InvalidOperationException ex) when (ex.Message.Contains("specific")) { // handle }

The key difference is that with the filter, if the condition is false, the exception is not considered handled by this catch block. It will continue to be examined by subsequent catch blocks or propagate up the call stack. In the traditional approach, you must explicitly rethrow, which loses the original stack trace unless you use throw; (which preserves it, but still requires manual logic).

Practical Example: Handling Specific Exception Types with Filters

Consider a method that reads a configuration file. You want to catch a FileNotFoundException only when the file name ends with .config:

try { var config = File.ReadAllText(path); } catch (FileNotFoundException ex) when (path.EndsWith(".config")) { // Only handle missing .config files Console.WriteLine($"Config file missing: {ex.FileName}"); }

If the condition is false, the exception propagates to the caller, which may have its own handling logic. This keeps the handling logic close to the operation and avoids catching exceptions that should be dealt with elsewhere.

Using catch when with Multiple Catch Blocks

Filters work naturally with multiple catch blocks. The runtime evaluates each catch block in order, and the first one whose type matches and whose filter returns true is selected. For example:

try { // ... } catch (ArgumentException ex) when (ex.ParamName == "id") { // specific argument error } catch (ArgumentException ex) { // any other argument error } catch (Exception ex) when (LogException(ex)) { // This catch block will never handle the exception because LogException returns false }

The third block uses a filter that calls a method. If LogException returns false, the exception is not handled here. This pattern can be used to log exceptions without swallowing them, but it has a subtle side effect: the filter method runs even if the exception would be handled by a later block? Actually no, it runs only when this catch block is reached. But it's a common pattern to log and then let the exception propagate.

Performance Considerations of Exception Filters

Exception filters can be more efficient than catching and rethrowing. When a filter returns false, the runtime continues searching for other handlers without unwinding the stack. This avoids the overhead of entering a catch block and then throwing again. However, the filter expression itself is evaluated, so if it performs expensive work, that cost is incurred for every exception that reaches that point.

A more important consideration is that filters are evaluated before the catch block, and if the filter throws an exception, that new exception replaces the original. This can obscure the original error. For example:

catch (Exception ex) when (ex.Data["key"] as string == "value") { // ... }

If ex.Data["key"] throws a KeyNotFoundException? Actually Data is a dictionary, accessing a missing key returns null, not throw. But if you call a method that throws, the new exception is thrown. So keep filter expressions simple and avoid operations that can fail.

Common Mistakes and Edge Cases

One common mistake is using a filter to modify state. The filter runs before the catch block, and if it returns false, the catch block never runs, but the state change in the filter still occurred. This can lead to unexpected behavior. For example:

bool handled = false; try { // ... } catch (Exception ex) when (handled = true) { // This always runs, but also sets handled to true. }

This is a common bug because the assignment expression returns the assigned value, so the filter always returns true. Use == instead.

Another edge case is that filters are not supported in all languages that compile to IL, but in C# they are available since version 6. If you're targeting an older compiler, you'll need to use the traditional approach.

When to Use catch when vs. Traditional if Checks

Use catch when when the condition is based on the exception's properties or external state that can be evaluated without side effects. It keeps the catch block focused on handling, and it allows the exception to continue propagating naturally if the condition isn't met. Use a traditional if inside the catch block when you need to perform complex logic that might itself throw, or when you need to rethrow with a different exception type. Filters are also useful when you want to log exceptions without handling them, but be aware of the side-effect risk.

c# catch when: Practical Usage and Code Examples | RYUSLOG DEV