diff --git a/docs/csharp/language-reference/operators/patterns.md b/docs/csharp/language-reference/operators/patterns.md
index 599d40e70d7a2..e086414bf4b95 100644
--- a/docs/csharp/language-reference/operators/patterns.md
+++ b/docs/csharp/language-reference/operators/patterns.md
@@ -1,7 +1,7 @@
---
title: "Patterns - Pattern matching using the is and switch expressions."
description: "Learn about the patterns supported by the `is` and `switch` expressions. Combine multiple patterns using the `and`, `or`, and `not` operators."
-ms.date: 01/30/2023
+ms.date: 11/14/2024
f1_keywords:
- "and_CSharpKeyword"
- "or_CSharpKeyword"
@@ -12,9 +12,9 @@ helpviewer_keywords:
- "or keyword [C#]"
- "not keyword [C#]"
---
-# Pattern matching - the `is` and `switch` expressions, and operators `and`, `or` and `not` in patterns
+# Pattern matching - the `is` and `switch` expressions, and operators `and`, `or`, and `not` in patterns
-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`.
+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`.
The following C# expressions and statements support pattern matching:
@@ -26,14 +26,14 @@ In those constructs, you can match an input expression against any of the follow
- [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.
- [Type pattern](#declaration-and-type-patterns): to check the run-time type of an expression.
-- [Constant pattern](#constant-pattern): to test if an expression result equals a specified constant.
+- [Constant pattern](#constant-pattern): to test that an expression result equals a specified constant.
- [Relational patterns](#relational-patterns): to compare an expression result with a specified constant.
-- [Logical patterns](#logical-patterns): to test if an expression matches a logical combination of patterns.
-- [Property pattern](#property-pattern): to test if an expression's properties or fields match nested patterns.
+- [Logical patterns](#logical-patterns): to test that an expression matches a logical combination of patterns.
+- [Property pattern](#property-pattern): to test that an expression's properties or fields match nested patterns.
- [Positional pattern](#positional-pattern): to deconstruct an expression result and test if the resulting values match nested patterns.
- [`var` pattern](#var-pattern): to match any expression and assign its result to a declared variable.
- [Discard pattern](#discard-pattern): to match any expression.
-- [List patterns](#list-patterns): to test if sequence elements match corresponding nested patterns. Introduced in C# 11.
+- [List patterns](#list-patterns): to test that a sequence of elements matches corresponding nested patterns. Introduced in C# 11.
[Logical](#logical-patterns), [property](#property-pattern), [positional](#positional-pattern), and [list](#list-patterns) patterns are *recursive* patterns. That is, they can contain *nested* patterns.
@@ -71,7 +71,7 @@ For that purpose you can use a *type pattern*, as the following example shows:
:::code language="csharp" source="snippets/patterns/DeclarationAndTypePatterns.cs" id="TypePattern":::
-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.
+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.
To check for non-null, you can use a [negated](#logical-patterns) `null` [constant pattern](#constant-pattern), as the following example shows:
@@ -145,20 +145,30 @@ As the preceding example shows, you can repeatedly use the pattern combinators i
### Precedence and order of checking
-The pattern combinators are ordered from the highest precedence to the lowest as follows:
+The pattern combinators are ordered based on the binding order of expressions as follows:
- `not`
- `and`
- `or`
-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.
+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:
-To explicitly specify the precedence, use parentheses, as the following example shows:
+:::code language="csharp" source="snippets/patterns/LogicalPatterns.cs" id="NegationWithoutParens":::
+
+The default binding means the previous example is parsed as the following example:
+
+:::code language="csharp" source="snippets/patterns/LogicalPatterns.cs" id="DefaultBinding":::
+
+To fix it, you must specify that you want the `not` to bind to the `>= 'a' and <= 'z'` expression:
+
+:::code language="csharp" source="snippets/patterns/LogicalPatterns.cs" id="SpecifyBindingOrder":::
+
+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:
:::code language="csharp" source="snippets/patterns/LogicalPatterns.cs" id="WithParentheses":::
> [!NOTE]
-> 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.
+> 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.
For more information, see the [Pattern combinators](~/_csharplang/proposals/csharp-9.0/patterns3.md#pattern-combinators) section of the feature proposal note.
@@ -270,7 +280,7 @@ Beginning with C# 11, you can match an array or a list against a *sequence* of p
:::code language="csharp" source="snippets/patterns/ListPattern.cs" id="BasicExample":::
-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:
+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:
:::code language="csharp" source="snippets/patterns/ListPattern.cs" id="MatchAnyElement":::
diff --git a/docs/csharp/language-reference/operators/snippets/patterns/LogicalPatterns.cs b/docs/csharp/language-reference/operators/snippets/patterns/LogicalPatterns.cs
index 7b1971fac366c..c2b2786547b57 100644
--- a/docs/csharp/language-reference/operators/snippets/patterns/LogicalPatterns.cs
+++ b/docs/csharp/language-reference/operators/snippets/patterns/LogicalPatterns.cs
@@ -55,6 +55,21 @@ private static void OrPattern()
//
}
+ //
+ // Incorrect pattern. `not` binds before `and`
+ static bool IsNotLowerCaseLetter(char c) => c is not >= 'a' and <= 'z';
+ //
+
+ //
+ // The default binding without parentheses is shows in this method. `not` binds before `and`
+ static bool IsNotLowerCaseLetterDefaultBinding(char c) => c is ((not >= 'a') and <= 'z');
+ //
+
+ //
+ // Correct pattern. Force `and` before `not`
+ static bool IsNotLowerCaseLetterParentheses(char c) => c is not (>= 'a' and <= 'z');
+ //
+
//
static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');
//
diff --git a/docs/csharp/language-reference/operators/snippets/patterns/patterns.csproj b/docs/csharp/language-reference/operators/snippets/patterns/patterns.csproj
index ce97aca9db5c1..f177ef30084fe 100644
--- a/docs/csharp/language-reference/operators/snippets/patterns/patterns.csproj
+++ b/docs/csharp/language-reference/operators/snippets/patterns/patterns.csproj
@@ -3,7 +3,7 @@
Exe
enable
- net8.0
+ net9.0
enable
Patterns