From e05761122629f6a3524a5f6c7f57d37a844dacff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:33:12 +0000 Subject: [PATCH 1/7] Initial plan From 465c5e2648cc111fd9f9a58953b8984c24d84a70 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:42:37 +0000 Subject: [PATCH 2/7] Add detailed explanation of when keyword and exception filters Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com> --- .../language-reference/keywords/when.md | 2 + .../exception-handling-statements.md | 36 +++++ .../WhenFilterExamples.cs | 152 ++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 docs/csharp/language-reference/statements/snippets/exception-handling-statements/WhenFilterExamples.cs diff --git a/docs/csharp/language-reference/keywords/when.md b/docs/csharp/language-reference/keywords/when.md index b0aafe972e071..9c2ad43a385de 100644 --- a/docs/csharp/language-reference/keywords/when.md +++ b/docs/csharp/language-reference/keywords/when.md @@ -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 depending on the text of the exception message. [!code-csharp[when-with-catch](~/samples/snippets/csharp/language-reference/keywords/when/catch.cs)] diff --git a/docs/csharp/language-reference/statements/exception-handling-statements.md b/docs/csharp/language-reference/statements/exception-handling-statements.md index ca4111d97edcd..2addf0cb19b33 100644 --- a/docs/csharp/language-reference/statements/exception-handling-statements.md +++ b/docs/csharp/language-reference/statements/exception-handling-statements.md @@ -95,6 +95,42 @@ 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. + +2. **Performance benefits**: If no filter matches, the exception continues propagating without the overhead of stack unwinding and restoration. + +3. **Cleaner code**: Multiple filters can handle different conditions of the same exception type without requiring nested if-else statements. + +4. **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"::: + +The exception filter approach is particularly valuable in production 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: diff --git a/docs/csharp/language-reference/statements/snippets/exception-handling-statements/WhenFilterExamples.cs b/docs/csharp/language-reference/statements/snippets/exception-handling-statements/WhenFilterExamples.cs new file mode 100644 index 0000000000000..0540af9e216a7 --- /dev/null +++ b/docs/csharp/language-reference/statements/snippets/exception-handling-statements/WhenFilterExamples.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace ExceptionFilterExamples +{ + public static class WhenFilterExamples + { + // + 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}"); + } + // + + // + 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"); + } + // + + // + public static void DemonstrateDebuggingAdvantage() + { + var contextData = new Dictionary + { + ["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 context) + { + Level2Method(context); + } + + private static void Level2Method(Dictionary context) + { + Level3Method(context); + } + + private static void Level3Method(Dictionary context) + { + throw new InvalidOperationException("Error in deep call stack"); + } + + private static bool LogAndFilter(Exception ex, Dictionary 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"); + } + // + } +} \ No newline at end of file From b2f3e32e0f08fdf05ce43d4c6ee9b239be1b1961 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 8 Aug 2025 10:20:42 -0400 Subject: [PATCH 3/7] Update docs/csharp/language-reference/statements/exception-handling-statements.md --- .../statements/exception-handling-statements.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/csharp/language-reference/statements/exception-handling-statements.md b/docs/csharp/language-reference/statements/exception-handling-statements.md index 2addf0cb19b33..23d8c25273f4c 100644 --- a/docs/csharp/language-reference/statements/exception-handling-statements.md +++ b/docs/csharp/language-reference/statements/exception-handling-statements.md @@ -109,12 +109,9 @@ Here's a comparison showing the difference: **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. - -2. **Performance benefits**: If no filter matches, the exception continues propagating without the overhead of stack unwinding and restoration. - -3. **Cleaner code**: Multiple filters can handle different conditions of the same exception type without requiring nested if-else statements. - -4. **Logging and diagnostics**: You can examine and log exception details before deciding whether to handle the exception: +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"::: From bfbf5d9881dc365bc35e239fd3c2cf486592fd8f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 8 Aug 2025 10:20:59 -0400 Subject: [PATCH 4/7] Update docs/csharp/language-reference/statements/exception-handling-statements.md --- .../statements/exception-handling-statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/csharp/language-reference/statements/exception-handling-statements.md b/docs/csharp/language-reference/statements/exception-handling-statements.md index 23d8c25273f4c..710230c7a014d 100644 --- a/docs/csharp/language-reference/statements/exception-handling-statements.md +++ b/docs/csharp/language-reference/statements/exception-handling-statements.md @@ -126,7 +126,7 @@ Use exception filters when you need to: :::code language="csharp" source="snippets/exception-handling-statements/WhenFilterExamples.cs" id="MultipleConditionsExample"::: -The exception filter approach is particularly valuable in production applications where preserving debugging information is crucial for diagnosing issues. +The exception filter approach is valuable in applications where preserving debugging information is crucial for diagnosing issues. #### Exceptions in async and iterator methods From 4b6db00ee430d407e2d2fbed8d34e1e525584bfd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:26:42 +0000 Subject: [PATCH 5/7] Address review feedback: Add stack trace preservation explanation Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com> --- .../statements/exception-handling-statements.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/csharp/language-reference/statements/exception-handling-statements.md b/docs/csharp/language-reference/statements/exception-handling-statements.md index 710230c7a014d..ecabf6a4c675b 100644 --- a/docs/csharp/language-reference/statements/exception-handling-statements.md +++ b/docs/csharp/language-reference/statements/exception-handling-statements.md @@ -126,6 +126,10 @@ Use exception filters when you need to: :::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 From a2ca51edd148e73610c7157423a6d27f3c5b347e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:15:51 +0000 Subject: [PATCH 6/7] Address review feedback: Fix punctuation, emphasis, headings and list formatting Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com> --- .../exception-handling-statements.md | 20 ++++++------- .../WhenFilterExamples.cs | 28 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/csharp/language-reference/statements/exception-handling-statements.md b/docs/csharp/language-reference/statements/exception-handling-statements.md index ecabf6a4c675b..c9e9928167865 100644 --- a/docs/csharp/language-reference/statements/exception-handling-statements.md +++ b/docs/csharp/language-reference/statements/exception-handling-statements.md @@ -100,18 +100,18 @@ If a `catch` clause has an exception filter, it can specify the exception type t 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. +- **Traditional `catch` blocks**: The catch block executes *after* the stack is unwound, 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:** +## 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: +- **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. +- **Performance benefits**: If no filter matches, the exception continues propagating without the overhead of stack unwinding and restoration. +- **Cleaner code**: Multiple filters can handle different conditions of the same exception type without requiring nested if-else statements. +- **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"::: @@ -119,10 +119,10 @@ Here's a comparison showing the difference: 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 +- 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"::: diff --git a/docs/csharp/language-reference/statements/snippets/exception-handling-statements/WhenFilterExamples.cs b/docs/csharp/language-reference/statements/snippets/exception-handling-statements/WhenFilterExamples.cs index 0540af9e216a7..9fa9f2419b166 100644 --- a/docs/csharp/language-reference/statements/snippets/exception-handling-statements/WhenFilterExamples.cs +++ b/docs/csharp/language-reference/statements/snippets/exception-handling-statements/WhenFilterExamples.cs @@ -17,9 +17,9 @@ public static void DemonstrateStackUnwindingDifference() } 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 + // 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}"); } @@ -30,8 +30,8 @@ public static void DemonstrateStackUnwindingDifference() } catch (InvalidOperationException ex) { - // Traditional catch: Stack already unwound - // Some debugging information may be lost + // Traditional catch: Stack already unwound. + // Some debugging information may be lost. if (ex.Message.Contains("traditional")) { Console.WriteLine($"Caught with if: {ex.Message}"); @@ -39,7 +39,7 @@ public static void DemonstrateStackUnwindingDifference() } else { - throw; // Re-throws and further modifies stack trace + throw; // Re-throws and further modifies stack trace. } } } @@ -60,7 +60,7 @@ public static void HandleFileOperations(string filePath) { try { - // Simulate file operation that might fail + // Simulate file operation that might fail. ProcessFile(filePath); } catch (IOException ex) when (ex.Message.Contains("access denied")) @@ -83,7 +83,7 @@ public static void HandleFileOperations(string filePath) private static void ProcessFile(string filePath) { - // Simulate different types of file exceptions + // Simulate different types of file exceptions. if (filePath.Contains("denied")) throw new IOException("File access denied"); if (filePath.Contains("missing")) @@ -110,13 +110,13 @@ public static void DemonstrateDebuggingAdvantage() try { - // Simulate a deep call stack + // 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 + // 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"); } } @@ -138,13 +138,13 @@ private static void Level3Method(Dictionary context) private static bool LogAndFilter(Exception ex, Dictionary context) { - // This method runs before stack unwinding - // Full call stack and local variables are still available + // 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 true to handle the exception, false to continue search. return ex.Message.Contains("deep call stack"); } // From 18fd2bee2c8adcc36fb4fe7c7a26bb3d266c28f1 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 11 Aug 2025 11:22:40 -0400 Subject: [PATCH 7/7] Apply suggestions from code review --- .../statements/exception-handling-statements.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/csharp/language-reference/statements/exception-handling-statements.md b/docs/csharp/language-reference/statements/exception-handling-statements.md index c9e9928167865..cdcfe5f89e328 100644 --- a/docs/csharp/language-reference/statements/exception-handling-statements.md +++ b/docs/csharp/language-reference/statements/exception-handling-statements.md @@ -115,7 +115,7 @@ Here's a comparison showing the difference: :::code language="csharp" source="snippets/exception-handling-statements/WhenFilterExamples.cs" id="DebuggingAdvantageExample"::: -**When to use exception filters:** +### When to use exception filters Use exception filters when you need to: @@ -126,7 +126,7 @@ Use exception filters when you need to: :::code language="csharp" source="snippets/exception-handling-statements/WhenFilterExamples.cs" id="MultipleConditionsExample"::: -**Stack trace preservation:** +### 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.