Back to Blog
C#

C# catch when: Using Exception Filters

Learn how to use the C# catch when clause to filter exceptions with precise conditions and understand how it differs from traditional catch blocks.

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

Exception filters in C# let a catch block run only when a Boolean condition is true. The catch when clause is useful when you want to handle an exception under specific circumstances and let it propagate naturally otherwise.

The catch when clause in C# attaches 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 avoids catching an exception unconditionally and then deciding whether to rethrow it.

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 call methods. Exception filters have been available in C# since version 6.

How Exception Filters Differ from Traditional 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, a false result means 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, and if you use throw;, the original stack trace is preserved.

Practical Example: Filtering on Exception Properties

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 not handle the exception if LogException returns false }

If LogException returns false, the exception is not handled by the third block and continues to propagate. This pattern is sometimes used to log exceptions without swallowing them, but it relies on a side effect in the filter, so use it with care.

Performance Considerations

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, avoiding the overhead of entering a catch block and 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.

Filters also run before the catch block, and if a filter throws an exception, the new exception replaces the original. Keep filter expressions simple and avoid operations that can fail. For example, ex.Data["key"] returns null for a missing key rather than throwing, but a method call inside a filter could throw and obscure the original error:

catch (Exception ex) when (IsExpected(ex)) { // ... }

If IsExpected throws, that exception is propagated instead of the original one.

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 bug because the assignment expression returns the assigned value, so the filter always returns true. Use == instead.

Another edge case is that exception filters are not available in every language that compiles to IL, but C# has supported them since version 6. If you are targeting an older compiler, use the traditional if and rethrow approach.

When to Use catch when Instead of an if Check

Use catch when when the condition can be evaluated without side effects and you want the exception to propagate naturally when the condition is false. It keeps the catch block focused on handling.

Use a traditional if inside the catch block when the condition involves complex logic that might itself throw, because a throwing condition inside a catch when filter would replace the original exception. A traditional if is also clearer when you need to perform additional checks after first catching the exception. Filters are also useful when you want to log exceptions without handling them, but be aware of the side-effect risk.

C# catch when: Syntax, Examples, and When to Use It | RYUSLOG DEV