Skip to content

Commit 19f90a8

Browse files
CopilotBillWagner
andauthored
Add explanation of why the when contextual keyword is better than if/else in catch blocks (#47887)
* Initial plan * Add detailed explanation of when keyword and exception filters Co-authored-by: BillWagner <[email protected]> * Update docs/csharp/language-reference/statements/exception-handling-statements.md * Update docs/csharp/language-reference/statements/exception-handling-statements.md * Address review feedback: Add stack trace preservation explanation Co-authored-by: BillWagner <[email protected]> * Address review feedback: Fix punctuation, emphasis, headings and list formatting Co-authored-by: BillWagner <[email protected]> * Apply suggestions from code review --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: BillWagner <[email protected]> Co-authored-by: Bill Wagner <[email protected]>
1 parent 959e1f5 commit 19f90a8

File tree

3 files changed

+191
-0
lines changed

3 files changed

+191
-0
lines changed

docs/csharp/language-reference/keywords/when.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ catch (ExceptionType [e]) when (expr)
2727

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

30+
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).
31+
3032
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.
3133

3234
[!code-csharp[when-with-catch](~/samples/snippets/csharp/language-reference/keywords/when/catch.cs)]

docs/csharp/language-reference/statements/exception-handling-statements.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,43 @@ You can provide several `catch` clauses for the same exception type if they dist
9595

9696
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.
9797

98+
##### Exception filters vs. traditional exception handling
99+
100+
Exception filters provide significant advantages over traditional exception handling approaches. The key difference is **when** the exception handling logic is evaluated:
101+
102+
- **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.
103+
- **Traditional `catch` blocks**: The catch block executes *after* the stack is unwound, potentially losing valuable debugging information.
104+
105+
Here's a comparison showing the difference:
106+
107+
:::code language="csharp" source="snippets/exception-handling-statements/WhenFilterExamples.cs" id="ExceptionFilterVsIfElse":::
108+
109+
## Advantages of exception filters
110+
111+
- **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.
112+
- **Performance benefits**: If no filter matches, the exception continues propagating without the overhead of stack unwinding and restoration.
113+
- **Cleaner code**: Multiple filters can handle different conditions of the same exception type without requiring nested if-else statements.
114+
- **Logging and diagnostics**: You can examine and log exception details before deciding whether to handle the exception:
115+
116+
:::code language="csharp" source="snippets/exception-handling-statements/WhenFilterExamples.cs" id="DebuggingAdvantageExample":::
117+
118+
### When to use exception filters
119+
120+
Use exception filters when you need to:
121+
122+
- Handle exceptions based on specific conditions or properties.
123+
- Preserve the original call stack for debugging.
124+
- Log or examine exceptions before deciding whether to handle them.
125+
- Handle the same exception type differently based on context.
126+
127+
:::code language="csharp" source="snippets/exception-handling-statements/WhenFilterExamples.cs" id="MultipleConditionsExample":::
128+
129+
### Stack trace preservation
130+
131+
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.
132+
133+
The exception filter approach is valuable in applications where preserving debugging information is crucial for diagnosing issues.
134+
98135
#### Exceptions in async and iterator methods
99136

100137
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:
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
5+
namespace ExceptionFilterExamples
6+
{
7+
public static class WhenFilterExamples
8+
{
9+
// <ExceptionFilterVsIfElse>
10+
public static void DemonstrateStackUnwindingDifference()
11+
{
12+
var localVariable = "Important debugging info";
13+
14+
try
15+
{
16+
ProcessWithExceptionFilter(localVariable);
17+
}
18+
catch (InvalidOperationException ex) when (ex.Message.Contains("filter"))
19+
{
20+
// Exception filter: Stack not unwound yet.
21+
// localVariable is still accessible in debugger.
22+
// Call stack shows original throwing location.
23+
Console.WriteLine($"Caught with filter: {ex.Message}");
24+
Console.WriteLine($"Local variable accessible: {localVariable}");
25+
}
26+
27+
try
28+
{
29+
ProcessWithTraditionalCatch(localVariable);
30+
}
31+
catch (InvalidOperationException ex)
32+
{
33+
// Traditional catch: Stack already unwound.
34+
// Some debugging information may be lost.
35+
if (ex.Message.Contains("traditional"))
36+
{
37+
Console.WriteLine($"Caught with if: {ex.Message}");
38+
Console.WriteLine($"Local variable accessible: {localVariable}");
39+
}
40+
else
41+
{
42+
throw; // Re-throws and further modifies stack trace.
43+
}
44+
}
45+
}
46+
47+
private static void ProcessWithExceptionFilter(string context)
48+
{
49+
throw new InvalidOperationException($"Exception for filter demo: {context}");
50+
}
51+
52+
private static void ProcessWithTraditionalCatch(string context)
53+
{
54+
throw new InvalidOperationException($"Exception for traditional demo: {context}");
55+
}
56+
// </ExceptionFilterVsIfElse>
57+
58+
// <MultipleConditionsExample>
59+
public static void HandleFileOperations(string filePath)
60+
{
61+
try
62+
{
63+
// Simulate file operation that might fail.
64+
ProcessFile(filePath);
65+
}
66+
catch (IOException ex) when (ex.Message.Contains("access denied"))
67+
{
68+
Console.WriteLine("File access denied. Check permissions.");
69+
}
70+
catch (IOException ex) when (ex.Message.Contains("not found"))
71+
{
72+
Console.WriteLine("File not found. Verify the path.");
73+
}
74+
catch (IOException ex) when (IsNetworkPath(filePath))
75+
{
76+
Console.WriteLine($"Network file operation failed: {ex.Message}");
77+
}
78+
catch (IOException)
79+
{
80+
Console.WriteLine("Other I/O error occurred.");
81+
}
82+
}
83+
84+
private static void ProcessFile(string filePath)
85+
{
86+
// Simulate different types of file exceptions.
87+
if (filePath.Contains("denied"))
88+
throw new IOException("File access denied");
89+
if (filePath.Contains("missing"))
90+
throw new IOException("File not found");
91+
if (IsNetworkPath(filePath))
92+
throw new IOException("Network timeout occurred");
93+
}
94+
95+
private static bool IsNetworkPath(string path)
96+
{
97+
return path.StartsWith(@"\\") || path.StartsWith("http");
98+
}
99+
// </MultipleConditionsExample>
100+
101+
// <DebuggingAdvantageExample>
102+
public static void DemonstrateDebuggingAdvantage()
103+
{
104+
var contextData = new Dictionary<string, object>
105+
{
106+
["RequestId"] = Guid.NewGuid(),
107+
["UserId"] = "user123",
108+
["Timestamp"] = DateTime.Now
109+
};
110+
111+
try
112+
{
113+
// Simulate a deep call stack.
114+
Level1Method(contextData);
115+
}
116+
catch (Exception ex) when (LogAndFilter(ex, contextData))
117+
{
118+
// This catch block may never execute if LogAndFilter returns false.
119+
// But LogAndFilter can examine the exception while the stack is intact.
120+
Console.WriteLine("Exception handled after logging");
121+
}
122+
}
123+
124+
private static void Level1Method(Dictionary<string, object> context)
125+
{
126+
Level2Method(context);
127+
}
128+
129+
private static void Level2Method(Dictionary<string, object> context)
130+
{
131+
Level3Method(context);
132+
}
133+
134+
private static void Level3Method(Dictionary<string, object> context)
135+
{
136+
throw new InvalidOperationException("Error in deep call stack");
137+
}
138+
139+
private static bool LogAndFilter(Exception ex, Dictionary<string, object> context)
140+
{
141+
// This method runs before stack unwinding.
142+
// Full call stack and local variables are still available.
143+
Console.WriteLine($"Exception occurred: {ex.Message}");
144+
Console.WriteLine($"Request ID: {context["RequestId"]}");
145+
Console.WriteLine($"Full stack trace preserved: {ex.StackTrace}");
146+
147+
// Return true to handle the exception, false to continue search.
148+
return ex.Message.Contains("deep call stack");
149+
}
150+
// </DebuggingAdvantageExample>
151+
}
152+
}

0 commit comments

Comments
 (0)