Skip to content

Commit e49964b

Browse files
BillWagnergewarren
andauthored
Add more details on pattern binding (#43574)
* Add more details on pattern binding Fixes #43177 We've had feedback that indicates confusion on how C# parses patterns with multiple `and`, `not`, and `or` patterns. Add more examples that demonstrates the problems. Explain the binding order, and recommend using parentheses when the pattern can be misinterpreted. Also, do a grammar check. * build error * Apply suggestions from code review Co-authored-by: Genevieve Warren <[email protected]> * grammar --------- Co-authored-by: Genevieve Warren <[email protected]>
1 parent bcec628 commit e49964b

File tree

3 files changed

+39
-14
lines changed

3 files changed

+39
-14
lines changed

docs/csharp/language-reference/operators/patterns.md

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "Patterns - Pattern matching using the is and switch expressions."
33
description: "Learn about the patterns supported by the `is` and `switch` expressions. Combine multiple patterns using the `and`, `or`, and `not` operators."
4-
ms.date: 01/30/2023
4+
ms.date: 11/14/2024
55
f1_keywords:
66
- "and_CSharpKeyword"
77
- "or_CSharpKeyword"
@@ -12,9 +12,9 @@ helpviewer_keywords:
1212
- "or keyword [C#]"
1313
- "not keyword [C#]"
1414
---
15-
# Pattern matching - the `is` and `switch` expressions, and operators `and`, `or` and `not` in patterns
15+
# Pattern matching - the `is` and `switch` expressions, and operators `and`, `or`, and `not` in patterns
1616

17-
You use the [`is` expression](is.md), the [switch statement](../statements/selection-statements.md#the-switch-statement) and the [switch expression](switch-expression.md) to match an input expression against any number of characteristics. C# supports multiple patterns, including declaration, type, constant, relational, property, list, var, and discard. Patterns can be combined using Boolean logic keywords `and`, `or`, and `not`.
17+
You use the [`is` expression](is.md), the [switch statement](../statements/selection-statements.md#the-switch-statement), and the [switch expression](switch-expression.md) to match an input expression against any number of characteristics. C# supports multiple patterns, including declaration, type, constant, relational, property, list, var, and discard. Patterns can be combined using Boolean logic keywords `and`, `or`, and `not`.
1818

1919
The following C# expressions and statements support pattern matching:
2020

@@ -26,14 +26,14 @@ In those constructs, you can match an input expression against any of the follow
2626

2727
- [Declaration pattern](#declaration-and-type-patterns): to check the run-time type of an expression and, if a match succeeds, assign an expression result to a declared variable.
2828
- [Type pattern](#declaration-and-type-patterns): to check the run-time type of an expression.
29-
- [Constant pattern](#constant-pattern): to test if an expression result equals a specified constant.
29+
- [Constant pattern](#constant-pattern): to test that an expression result equals a specified constant.
3030
- [Relational patterns](#relational-patterns): to compare an expression result with a specified constant.
31-
- [Logical patterns](#logical-patterns): to test if an expression matches a logical combination of patterns.
32-
- [Property pattern](#property-pattern): to test if an expression's properties or fields match nested patterns.
31+
- [Logical patterns](#logical-patterns): to test that an expression matches a logical combination of patterns.
32+
- [Property pattern](#property-pattern): to test that an expression's properties or fields match nested patterns.
3333
- [Positional pattern](#positional-pattern): to deconstruct an expression result and test if the resulting values match nested patterns.
3434
- [`var` pattern](#var-pattern): to match any expression and assign its result to a declared variable.
3535
- [Discard pattern](#discard-pattern): to match any expression.
36-
- [List patterns](#list-patterns): to test if sequence elements match corresponding nested patterns. Introduced in C# 11.
36+
- [List patterns](#list-patterns): to test that a sequence of elements matches corresponding nested patterns. Introduced in C# 11.
3737

3838
[Logical](#logical-patterns), [property](#property-pattern), [positional](#positional-pattern), and [list](#list-patterns) patterns are *recursive* patterns. That is, they can contain *nested* patterns.
3939

@@ -71,7 +71,7 @@ For that purpose you can use a *type pattern*, as the following example shows:
7171

7272
:::code language="csharp" source="snippets/patterns/DeclarationAndTypePatterns.cs" id="TypePattern":::
7373

74-
Like a declaration pattern, a type pattern matches an expression when an expression result is non-null and its run-time type satisfies any of the conditions listed above.
74+
Like a declaration pattern, a type pattern matches an expression when an expression result is non-null and its run-time type satisfies any of the preceding conditions.
7575

7676
To check for non-null, you can use a [negated](#logical-patterns) `null` [constant pattern](#constant-pattern), as the following example shows:
7777

@@ -145,20 +145,30 @@ As the preceding example shows, you can repeatedly use the pattern combinators i
145145

146146
### Precedence and order of checking
147147

148-
The pattern combinators are ordered from the highest precedence to the lowest as follows:
148+
The pattern combinators are ordered based on the binding order of expressions as follows:
149149

150150
- `not`
151151
- `and`
152152
- `or`
153153

154-
When a logical pattern is a pattern of an `is` expression, the precedence of logical pattern combinators is **higher** than the precedence of logical operators (both [bitwise logical](bitwise-and-shift-operators.md) and [Boolean logical](boolean-logical-operators.md) operators). Otherwise, the precedence of logical pattern combinators is **lower** than the precedence of logical and conditional logical operators. For the complete list of C# operators ordered by precedence level, see the [Operator precedence](index.md#operator-precedence) section of the [C# operators](index.md) article.
154+
The `not` pattern binds to its operand first. The `and` pattern binds after any `not` pattern expression binding. The `or` pattern binds after all `not` and `and` patterns are bound to operands. The following example tries to match all characters that aren't lower case letters, `a` - `z`. It has an error, because the `not` pattern binds before the `and` pattern:
155155

156-
To explicitly specify the precedence, use parentheses, as the following example shows:
156+
:::code language="csharp" source="snippets/patterns/LogicalPatterns.cs" id="NegationWithoutParens":::
157+
158+
The default binding means the previous example is parsed as the following example:
159+
160+
:::code language="csharp" source="snippets/patterns/LogicalPatterns.cs" id="DefaultBinding":::
161+
162+
To fix it, you must specify that you want the `not` to bind to the `>= 'a' and <= 'z'` expression:
163+
164+
:::code language="csharp" source="snippets/patterns/LogicalPatterns.cs" id="SpecifyBindingOrder":::
165+
166+
Adding parentheses becomes more important as your patterns become more complicated. In general, use parentheses to clarify your patterns for other developers, as the following example shows:
157167

158168
:::code language="csharp" source="snippets/patterns/LogicalPatterns.cs" id="WithParentheses":::
159169

160170
> [!NOTE]
161-
> The order in which patterns are checked is undefined. At run time, the right-hand nested patterns of `or` and `and` patterns can be checked first.
171+
> The order in which patterns having the same binding order are checked is undefined. At run time, the right-hand nested patterns of multiple `or` patterns and multiple `and` patterns can be checked first.
162172
163173
For more information, see the [Pattern combinators](~/_csharplang/proposals/csharp-9.0/patterns3.md#pattern-combinators) section of the feature proposal note.
164174

@@ -270,7 +280,7 @@ Beginning with C# 11, you can match an array or a list against a *sequence* of p
270280

271281
:::code language="csharp" source="snippets/patterns/ListPattern.cs" id="BasicExample":::
272282

273-
As the preceding example shows, a list pattern is matched when each nested pattern is matched by the corresponding element of an input sequence. You can use any pattern within a list pattern. To match any element, use the [discard pattern](#discard-pattern) or, if you also want to capture the element, the [var pattern](#var-pattern), as the following example shows:
283+
As the preceding example shows, a list pattern is matched when each nested pattern matches the corresponding element of an input sequence. You can use any pattern within a list pattern. To match any element, use the [discard pattern](#discard-pattern) or, if you also want to capture the element, the [var pattern](#var-pattern), as the following example shows:
274284

275285
:::code language="csharp" source="snippets/patterns/ListPattern.cs" id="MatchAnyElement":::
276286

docs/csharp/language-reference/operators/snippets/patterns/LogicalPatterns.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,21 @@ private static void OrPattern()
5555
// </OrPattern>
5656
}
5757

58+
// <NegationWithoutParens>
59+
// Incorrect pattern. `not` binds before `and`
60+
static bool IsNotLowerCaseLetter(char c) => c is not >= 'a' and <= 'z';
61+
// </NegationWithoutParens>
62+
63+
// <DefaultBinding>
64+
// The default binding without parentheses is shows in this method. `not` binds before `and`
65+
static bool IsNotLowerCaseLetterDefaultBinding(char c) => c is ((not >= 'a') and <= 'z');
66+
// </DefaultBinding>
67+
68+
// <SpecifyBindingOrder>
69+
// Correct pattern. Force `and` before `not`
70+
static bool IsNotLowerCaseLetterParentheses(char c) => c is not (>= 'a' and <= 'z');
71+
// </SpecifyBindingOrder>
72+
5873
// <WithParentheses>
5974
static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');
6075
// </WithParentheses>

docs/csharp/language-reference/operators/snippets/patterns/patterns.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
55
<ImplicitUsings>enable</ImplicitUsings>
6-
<TargetFramework>net8.0</TargetFramework>
6+
<TargetFramework>net9.0</TargetFramework>
77
<Nullable>enable</Nullable>
88
<RootNamespace>Patterns</RootNamespace>
99
</PropertyGroup>

0 commit comments

Comments
 (0)