[C#] Catching Exceptions Under Specific Conditions Using Exception Filters (when clause)

Introduced in C# 6.0, Exception Filters allow you to specify detailed conditions for entering a catch block. This makes it possible to decide whether to catch an exception based not only on the exception type but also on the values of properties within the exception object or external states.

Compared to the traditional method of “catching first, checking with an if statement, and re-throwing with throw if it’s not the target,” this approach improves code readability and keeps stack trace information more accurate during debugging.

This article explains how to handle “unique constraint violations (duplicate errors)” and “connection errors” in database operations as separate processes, even though they are the same exception type.

目次

Basic Syntax of Exception Filters

Write the when keyword after the catch clause, followed by a conditional expression that returns a bool.

catch (ExceptionType ex) when (condition)
{
    // Executed only if the condition is true
}

Practical Code Example: Branching Logic by Error Code

The following code simulates a class for database operations, performing different exception handling depending on the error code. Database exceptions (such as SqlException) typically hold a numeric code indicating the type of error (primary key duplication, connection timeout, etc.).

Note: Here, we define a custom DatabaseException class for explanation purposes so that the code runs with just the standard library.

using System;

namespace DatabaseApp
{
    // Custom exception simulating database errors
    public class DatabaseException : Exception
    {
        public int ErrorCode { get; }

        public DatabaseException(string message, int errorCode) : base(message)
        {
            ErrorCode = errorCode;
        }
    }

    class Program
    {
        // Error code constants
        private const int ErrorDuplicateKey = 2601; // Duplicate error
        private const int ErrorConnectionFailed = 53;   // Connection error

        static void Main()
        {
            try
            {
                // Execute user registration (assume an exception occurs here)
                // Change the argument to verify behavior:
                // 1: Duplicate error, 2: Connection error, 3: Other error
                RegisterUser("user01", 1);
            }
            // 1. Catch only if error code is "Duplicate Error (2601)"
            catch (DatabaseException ex) when (ex.ErrorCode == ErrorDuplicateKey)
            {
                Console.WriteLine("[Handled] User ID is duplicated. Please specify a different ID.");
                Console.WriteLine($"Detail: {ex.Message}");
            }
            // 2. Catch only if error code is "Connection Error (53)"
            catch (DatabaseException ex) when (ex.ErrorCode == ErrorConnectionFailed)
            {
                Console.WriteLine("[Handled] Cannot connect to database. Please retry.");
                Console.WriteLine($"Detail: {ex.Message}");
            }
            // 3. Catch DatabaseException not matching the filters above
            catch (DatabaseException ex)
            {
                Console.WriteLine("[Generic] A database error occurred.");
                Console.WriteLine($"Code: {ex.ErrorCode}, Message: {ex.Message}");
            }
            // 4. Other unexpected exceptions
            catch (Exception ex)
            {
                Console.WriteLine($"Unexpected error: {ex.Message}");
            }
        }

        // Method simulating database registration
        static void RegisterUser(string userId, int scenarioType)
        {
            Console.WriteLine($"Registering user '{userId}'...");

            switch (scenarioType)
            {
                case 1:
                    throw new DatabaseException("Primary key violation", ErrorDuplicateKey);
                case 2:
                    throw new DatabaseException("Network path not found", ErrorConnectionFailed);
                default:
                    throw new DatabaseException("Unknown database error", 9999);
            }
        }
    }
}

Execution Result (when scenarioType = 1)

Registering user 'user01'...
[Handled] User ID is duplicated. Please specify a different ID.
Detail: Primary key violation

Technical Points and Benefits

1. Preservation of Stack Trace (Preventing Stack Unwinding)

The biggest technical benefit of using the when clause is that “if the condition is not met, the stack is not unwound.”

If you use an if statement inside a catch block to branch logic and then re-throw with throw; when the condition isn’t met, the stack unwinds to that catch block. This can cause the debugger to highlight the throw line instead of the original error location, or cause some context information to be lost.

With exception filters, an exception that does not match the condition is treated as if it were never caught, maintaining the original stack information.

2. Application to Logging

Exception filters can be used in a tricky way to output logs without catching the exception.

catch (Exception ex) when (LogException(ex))
{
    // This block is never reached
}

// Method that always returns false
static bool LogException(Exception ex)
{
    Console.WriteLine($"Log output: {ex.Message}");
    return false; // By returning false, the catch block is not entered
}

Using this pattern allows you to record passing exceptions without interrupting or modifying the program’s execution flow (though use this with caution as it can make the control flow harder to understand).

3. Improved Readability of Multiple Catches

When you want to implement different recovery logic for the same exception type based on error codes or message content, using when clauses allows you to write each process as an independent catch block. This prevents the creation of massive if-else statements inside a single catch block.

Summary

Exception filters (when clause) are a powerful feature for granular exception handling based on specific error codes or conditions. They are strongly recommended to improve code robustness and readability, especially in cases like database operations or HTTP communications where a single exception type can encompass a wide variety of error causes.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次