Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/csharp/language-reference/keywords/when.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ catch (ExceptionType [e]) when (expr)

where *expr* is an expression that evaluates to a Boolean value. If it returns `true`, the exception handler executes; if `false`, it does not.

Exception filters with the `when` keyword provide several advantages over traditional exception handling approaches, including better debugging support and performance benefits. For a detailed explanation of how exception filters preserve the call stack and improve debugging, see [Exception filters vs. traditional exception handling](../statements/exception-handling-statements.md#exception-filters-vs-traditional-exception-handling).

The following example uses the `when` keyword to conditionally execute handlers for an <xref:System.Net.Http.HttpRequestException> depending on the text of the exception message.

[!code-csharp[when-with-catch](~/samples/snippets/csharp/language-reference/keywords/when/catch.cs)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,43 @@ You can provide several `catch` clauses for the same exception type if they dist

If a `catch` clause has an exception filter, it can specify the exception type that is the same as or less derived than an exception type of a `catch` clause that appears after it. For example, if an exception filter is present, a `catch (Exception e)` clause doesn't need to be the last clause.

##### Exception filters vs. traditional exception handling

Exception filters provide significant advantages over traditional exception handling approaches. The key difference is **when** the exception handling logic is evaluated:

- **Exception filters (`when`)**: The filter expression is evaluated *before* the stack is unwound. This means the original call stack and all local variables remain intact during filter evaluation.
- **Traditional `catch` blocks**: The stack is unwound *before* entering the catch block, potentially losing valuable debugging information.

Here's a comparison showing the difference:

:::code language="csharp" source="snippets/exception-handling-statements/WhenFilterExamples.cs" id="ExceptionFilterVsIfElse":::

**Advantages of exception filters:**

1. **Better debugging experience**: Since the stack isn't unwound until a filter matches, debuggers can show the original point of failure with all local variables intact.
1. **Performance benefits**: If no filter matches, the exception continues propagating without the overhead of stack unwinding and restoration.
1. **Cleaner code**: Multiple filters can handle different conditions of the same exception type without requiring nested if-else statements.
1. **Logging and diagnostics**: You can examine and log exception details before deciding whether to handle the exception:

:::code language="csharp" source="snippets/exception-handling-statements/WhenFilterExamples.cs" id="DebuggingAdvantageExample":::

**When to use exception filters:**

Use exception filters when you need to:

- Handle exceptions based on specific conditions or properties
- Preserve the original call stack for debugging
- Log or examine exceptions before deciding whether to handle them
- Handle the same exception type differently based on context

:::code language="csharp" source="snippets/exception-handling-statements/WhenFilterExamples.cs" id="MultipleConditionsExample":::

**Stack trace preservation:**

Exception filters preserve the original `ex.StackTrace` property. If a `catch` clause can't process the exception and re-throws, the original stack information is lost. The `when` filter doesn't unwind the stack, so if a `when` filter is `false`, the original stack trace isn't changed.

The exception filter approach is valuable in applications where preserving debugging information is crucial for diagnosing issues.

#### Exceptions in async and iterator methods

If an exception occurs in an [async function](../keywords/async.md), it propagates to the caller of the function when you [await](../operators/await.md) the result of the function, as the following example shows:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.IO;

namespace ExceptionFilterExamples
{
public static class WhenFilterExamples
{
// <ExceptionFilterVsIfElse>
public static void DemonstrateStackUnwindingDifference()
{
var localVariable = "Important debugging info";

try
{
ProcessWithExceptionFilter(localVariable);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("filter"))
{
// Exception filter: Stack not unwound yet
// localVariable is still accessible in debugger
// Call stack shows original throwing location
Console.WriteLine($"Caught with filter: {ex.Message}");
Console.WriteLine($"Local variable accessible: {localVariable}");
}

try
{
ProcessWithTraditionalCatch(localVariable);
}
catch (InvalidOperationException ex)
{
// Traditional catch: Stack already unwound
// Some debugging information may be lost
if (ex.Message.Contains("traditional"))
{
Console.WriteLine($"Caught with if: {ex.Message}");
Console.WriteLine($"Local variable accessible: {localVariable}");
}
else
{
throw; // Re-throws and further modifies stack trace
}
}
}

private static void ProcessWithExceptionFilter(string context)
{
throw new InvalidOperationException($"Exception for filter demo: {context}");
}

private static void ProcessWithTraditionalCatch(string context)
{
throw new InvalidOperationException($"Exception for traditional demo: {context}");
}
// </ExceptionFilterVsIfElse>

// <MultipleConditionsExample>
public static void HandleFileOperations(string filePath)
{
try
{
// Simulate file operation that might fail
ProcessFile(filePath);
}
catch (IOException ex) when (ex.Message.Contains("access denied"))
{
Console.WriteLine("File access denied. Check permissions.");
}
catch (IOException ex) when (ex.Message.Contains("not found"))
{
Console.WriteLine("File not found. Verify the path.");
}
catch (IOException ex) when (IsNetworkPath(filePath))
{
Console.WriteLine($"Network file operation failed: {ex.Message}");
}
catch (IOException)
{
Console.WriteLine("Other I/O error occurred.");
}
}

private static void ProcessFile(string filePath)
{
// Simulate different types of file exceptions
if (filePath.Contains("denied"))
throw new IOException("File access denied");
if (filePath.Contains("missing"))
throw new IOException("File not found");
if (IsNetworkPath(filePath))
throw new IOException("Network timeout occurred");
}

private static bool IsNetworkPath(string path)
{
return path.StartsWith(@"\\") || path.StartsWith("http");
}
// </MultipleConditionsExample>

// <DebuggingAdvantageExample>
public static void DemonstrateDebuggingAdvantage()
{
var contextData = new Dictionary<string, object>
{
["RequestId"] = Guid.NewGuid(),
["UserId"] = "user123",
["Timestamp"] = DateTime.Now
};

try
{
// Simulate a deep call stack
Level1Method(contextData);
}
catch (Exception ex) when (LogAndFilter(ex, contextData))
{
// This catch block may never execute if LogAndFilter returns false
// But LogAndFilter can examine the exception while the stack is intact
Console.WriteLine("Exception handled after logging");
}
}

private static void Level1Method(Dictionary<string, object> context)
{
Level2Method(context);
}

private static void Level2Method(Dictionary<string, object> context)
{
Level3Method(context);
}

private static void Level3Method(Dictionary<string, object> context)
{
throw new InvalidOperationException("Error in deep call stack");
}

private static bool LogAndFilter(Exception ex, Dictionary<string, object> context)
{
// This method runs before stack unwinding
// Full call stack and local variables are still available
Console.WriteLine($"Exception occurred: {ex.Message}");
Console.WriteLine($"Request ID: {context["RequestId"]}");
Console.WriteLine($"Full stack trace preserved: {ex.StackTrace}");

// Return true to handle the exception, false to continue search
return ex.Message.Contains("deep call stack");
}
// </DebuggingAdvantageExample>
}
}