Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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/fundamentals/expressions/operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ Relational operators compare two values and return a `bool`.

Relational operators work on all numeric types and `char`. For `char`, comparison uses the character's numeric Unicode code point value, not any alphabetical or domain-specific ordering. In the grade example above, `'B'` is greater than or equal to `'A'` because `'B'` has Unicode value 66 and `'A'` has Unicode value 65 — the *numbers* determine the comparison, not the meaning of the letter grades.

The same symbols can form [relational patterns](../patterns/relational-logical-patterns.md) in an `is` expression or `switch`. For example, `temperature < 0` is a relational expression that returns a `bool`, while `temperature is < 0` applies the relational pattern `< 0` to the value of `temperature`.

## Equality operators

`==` and `!=` check whether two values are equal or not. `!=` is `true` when the operands are **not** equal, and `false` when they are.
Expand Down
64 changes: 64 additions & 0 deletions docs/csharp/fundamentals/patterns/list-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: "List and slice patterns"
description: Learn when to use C# list patterns to test a sequence's shape and selected elements, and slice patterns to allow unmatched elements.
ms.date: 09/17/2026
ms.topic: concept-article
ai-usage: ai-assisted
---

# List and slice patterns

> [!TIP]
> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. Start with the [pattern matching overview](pattern-matching.md) if patterns are new to you. For complete supported-type and language rules, see [list patterns](../../language-reference/operators/patterns.md#list-patterns) in the language reference.

A *list pattern* tests the shape of an array, list, or another supported sequence and applies nested patterns to selected elements. Shape includes the number and positions of elements. A *slice pattern*, written `..`, allows a list pattern to contain zero or more elements that aren't tested individually.

List patterns don't make every <xref:System.Collections.Generic.IEnumerable%601> input matchable. The input's compile-time type must support the length or count and element access required by list-pattern rules. Arrays, `List<T>`, strings, and spans are common examples.

## Match an exact shape

The following method recognizes a two-column header:

:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="ExactListPattern":::

The `columns` expression is the pattern input. `["Name", "Score"]` contains two constant patterns. Without a slice pattern, the length must be exactly two, and each nested pattern must match the element in the same position. A longer array doesn't match even if its first two elements are the same.

Choose a list pattern when both the sequence shape and selected element values express the decision. If only the number of elements matters, a `Length` or `Count` property pattern, such as `items is { Count: 0 }`, states that intent more directly.

## Match selected elements with discards

The following method reads the winner and third-place finisher from a three-name finishing order:

:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="CaptureElements":::

`var winner` and `var thirdPlace` capture elements that the result uses. The discard pattern `_` accepts the second element without retaining it. Because there's no `..`, the list must contain exactly three elements.

Choose this form when fixed positions have stable meaning. Use a loop or LINQ when you need to inspect an arbitrary number of elements, transform a sequence, search throughout it, or perform aggregation.

## Allow remaining elements with a slice pattern

A command line can start with `--verbose`, contain other arguments, and end with the input file name. The following method recognizes that shape and captures the file name:

:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="SlicePattern":::

The slice pattern `..` matches zero or more elements between the first and last elements. A list pattern can contain at most one slice pattern. In this example, the program doesn't need the middle arguments, so the slice has no nested pattern or variable.

A slice can appear at the beginning, middle, or end of a list pattern. Use it when the elements around the slice are the meaningful part of the shape. Don't use a list pattern to replace ordinary iteration when every element needs processing.

## Apply a pattern to a slice

You can apply another pattern to the part matched by `..`. The following method tests whether an array starts with `"BEGIN"`, ends with `"END"`, and has at least one element between them:

:::code language="csharp" source="snippets/patterns/ListPatterns.cs" ID="SliceSubpattern":::

The outer pattern first requires `"BEGIN"` and `"END"` at the boundaries. The property pattern `{ Length: > 0 }` then tests the slice between them.

Use a slice subpattern only when the middle portion itself needs a test or capture. If only boundary elements matter, plain `..` is simpler.

## See also

- [Pattern matching overview](pattern-matching.md)
- [Property and positional patterns](property-positional-patterns.md)
- [List pattern reference](../../language-reference/operators/patterns.md#list-patterns)
- [Arrays](../../language-reference/builtin-types/arrays.md)
- [Use a `foreach` statement to iterate through a collection](../statements/collections.md)
11 changes: 7 additions & 4 deletions docs/csharp/fundamentals/patterns/pattern-matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,20 @@ C# includes patterns for common kinds of data tests:
| --- | --- |
| [Declaration, constant, and `var` patterns](declaration-constant-var-patterns.md) | A run-time type, a specific constant value, or any value that you want to capture |
| [Type patterns](type-patterns.md) | A run-time type without declaring a variable |
| Property and positional patterns | Properties, fields, or deconstructed values |
| Relational and logical patterns | Comparisons and combinations such as `and`, `or`, and `not` |
| List patterns | The values and shape of a list or array |
| [Property and positional patterns](property-positional-patterns.md) | Properties, fields, or deconstructed values |
| [Relational, logical, and parenthesized patterns](relational-logical-patterns.md) | Comparisons and combinations such as `and`, `or`, and `not` |
| [List and slice patterns](list-patterns.md) | The values and shape of a supported sequence |
| [Discard patterns and discards](discards.md) | Any remaining value, or a value your code intentionally ignores |

The Fundamentals articles linked in the table provide focused coverage of the categories currently documented in this section. For complete syntax and examples for all pattern categories, see the [patterns reference](../../language-reference/operators/patterns.md).
The linked Fundamentals articles explain when to choose each category. For complete syntax and examples, see the [patterns reference](../../language-reference/operators/patterns.md).

## See also

- [Declaration, constant, and `var` patterns](declaration-constant-var-patterns.md)
- [Type patterns](type-patterns.md)
- [Property and positional patterns](property-positional-patterns.md)
- [Relational, logical, and parenthesized patterns](relational-logical-patterns.md)
- [List and slice patterns](list-patterns.md)
- [Discards](discards.md)
- [Patterns reference](../../language-reference/operators/patterns.md)
- [`switch` expression reference](../../language-reference/operators/switch-expression.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: "Property and positional patterns"
description: Learn when to use C# property patterns to test named members and positional patterns to test deconstructed or tuple values.
ms.date: 09/17/2026
ms.topic: concept-article
ai-usage: ai-assisted
---

# Property and positional patterns

> [!TIP]
> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. Start with the [pattern matching overview](pattern-matching.md) if patterns are new to you. For complete language rules, see [property patterns](../../language-reference/operators/patterns.md#property-pattern) and [positional patterns](../../language-reference/operators/patterns.md#positional-pattern) in the language reference.

Property and positional patterns test parts of a value. A *property pattern* names the properties or fields to test. A *positional pattern* tests values produced by deconstructing an object or tuple.

Both are *recursive patterns*: Each member or position has its own nested pattern. The input to the outer pattern is an expression. C# evaluates that expression, then applies each nested pattern to the corresponding part of the evaluated value.

## Test named members with a property pattern

The following method tests two named properties of a weather reading, with temperature values in degrees Celsius:

:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="PropertyPattern":::

The `reading` expression is the input to the outer pattern. The pattern matches only when its evaluated value is non-null and both nested patterns match:

- The relational pattern `> 30` tests the value of `TemperatureC`.
- The relational pattern `> 70` tests the value of `HumidityPercent`.

Choose a property pattern when member names help explain the test. Unlike a series of Boolean expressions, the pattern groups the relevant shape and values in one description. For one simple comparison, such as `reading.TemperatureC > 30`, an ordinary relational expression is often clearer.

You can add a type test before the braces when the input expression can produce different types. You can also use a member path to test a nested property:

:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="NestedPropertyPattern":::

`DateTime { Date.DayOfWeek: DayOfWeek.Saturday or DayOfWeek.Sunday }` first tests that the evaluated value is a <xref:System.DateTime>. It then follows the `Date.DayOfWeek` member path and tests the day against two constant patterns. The pattern doesn't match if the outer value is `null` or the type test fails. In general, a property pattern also doesn't match if an object needed along a member path is `null`.

Choose named properties over positions when readers would need to memorize what each position means.

## Test a stable shape with a positional pattern

A *positional pattern* deconstructs a value and applies nested patterns in order. A type can define that order with a `Deconstruct` method. Positional records provide deconstruction automatically.

The following `GridPoint` record has an `X` coordinate followed by a `Y` coordinate. The method classifies a point by its position relative to the axes:

:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="PositionalPattern":::

The `point` expression is the pattern input. For `(0, 0)`, C# evaluates `point`, deconstructs the non-null value into its `X` and `Y` components, and applies a constant pattern to each component. The discard pattern `_` accepts a component that doesn't matter to that arm.

The positions must follow the type's deconstruction order. Choose a positional pattern when that order is a deliberate, stable part of the type's design, such as `(X, Y)`. Use a property pattern when names communicate the test better or when the deconstruction order is difficult to remember.

## Match a tuple of related inputs

A tuple combines multiple values into one value with a fixed positional shape. The following method uses a signal value and a Boolean value to choose one result:

:::code language="csharp" source="snippets/patterns/PropertyPositionalPatterns.cs" ID="TuplePattern":::

The tuple expression `(signal, crossingIsClear)` is the input. Each switch arm applies a positional pattern to both tuple elements. This form keeps each combination next to its result.

Choose a tuple pattern when several small, related inputs jointly determine one result. If the positions need extensive explanation or the data belongs together throughout the program, define a type with named properties instead.

## See also

- [Pattern matching overview](pattern-matching.md)
- [Relational, logical, and parenthesized patterns](relational-logical-patterns.md)
- [Deconstructing tuples and other types](../functional/deconstruct.md)
- [Property pattern reference](../../language-reference/operators/patterns.md#property-pattern)
- [Positional pattern reference](../../language-reference/operators/patterns.md#positional-pattern)
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: "Relational, logical, and parenthesized patterns"
description: Learn how C# relational patterns compare values and how logical and parenthesized patterns combine pattern tests.
ms.date: 09/17/2026
ms.topic: concept-article
ai-usage: ai-assisted
---

# Relational, logical, and parenthesized patterns

> [!TIP]
> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. Start with the [pattern matching overview](pattern-matching.md) if patterns are new to you. For complete language rules, see [relational patterns](../../language-reference/operators/patterns.md#relational-patterns) and [logical patterns](../../language-reference/operators/patterns.md#logical-patterns) in the language reference.

A *relational pattern* compares an evaluated value with a constant by using `<`, `>`, `<=`, or `>=`. *Logical patterns* combine or negate patterns with the pattern operators `and`, `or`, and `not`. A *parenthesized pattern* uses parentheses to make the intended grouping explicit or to change the default grouping.

## Distinguish expressions from patterns

The same relational symbol can appear in an ordinary expression or in a pattern. The following example uses both forms with a temperature:

:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="ExpressionAndPattern":::

`temperature < 0` is a *relational expression*. It has a left operand and a right operand, and produces a `bool`.

In `temperature is < 0`, `temperature` is the pattern input expression. C# evaluates it, and the relational pattern `< 0` tests the resulting value. In the switch arm `< 0 => "Freezing"`, the expression before `switch` supplies the input, so the pattern contains only `< 0`.

The example displays both Boolean results to show that the two tests classify the same temperature. The switch expression maps the value to a description.

Choose a relational expression for one direct comparison. Choose relational patterns when the comparison is part of a larger pattern or when several ranges map cleanly to switch results.

## Describe ranges with `and`

The following pattern tests whether a temperature is in the inclusive range from 18 through 24:

:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="AndPattern":::

The input expression is `temperature`. The logical pattern `>= 18 and <= 24` combines two relational patterns that both test the same evaluated value. The `and` pattern matches only when both nested patterns match.

`and` is a pattern operator here, not the conditional-AND Boolean operator `&&`. Pattern matching describes what must match. Don't rely on nested patterns being tested left to right or short-circuiting like Boolean operands.

## Describe alternatives with `or` and exclusions with `not`

The following methods test a day of the week and a simple status value:

:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="OrNotPatterns":::

`DayOfWeek.Saturday or DayOfWeek.Sunday` is one logical pattern composed of two constant patterns. It matches when either nested pattern matches. `IsActive` uses the `not` pattern to exclude `Status.Complete`.

`not` is a pattern operator, not the Boolean negation operator `!`. Choose `or` when several pattern alternatives have the same result. Choose `not` when expressing the excluded pattern is clearer than listing every accepted value.

## Group patterns with parentheses

Pattern operators bind in this order:

1. `not`
1. `and`
1. `or`

The following test accepts priorities 1 through 3 or the special priority 9:

:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="ParenthesizedPattern":::

The parentheses aren't required for the compiler because `and` binds before `or`, but they make the two alternatives visible: the range from 1 through 3, or 9. Use parentheses whenever a reader might hesitate over the grouping. Parentheses can also change the default grouping, such as `not (>= 1 and <= 3)`.

## Use a `when` guard for a separate condition

Logical patterns work best when nested patterns describe the input value itself. A `when` guard is an additional Boolean condition on a `case` label or switch arm. Use a guard when the decision also depends on information that isn't naturally part of the pattern.

The following warning depends on the temperature and a separate `isOutdoors` value:

:::code language="csharp" source="snippets/patterns/RelationalLogicalPatterns.cs" ID="WhenGuard":::

The relational pattern `> 35` describes the `temperature` input. The guard `when isOutdoors` checks a separate value. A guard is also preferable when the condition needs a method call or a Boolean expression that pattern syntax doesn't express clearly.

## See also

- [Pattern matching overview](pattern-matching.md)
- [Property and positional patterns](property-positional-patterns.md)
- [C# operators](../expressions/operators.md)
- [Relational pattern reference](../../language-reference/operators/patterns.md#relational-patterns)
- [Logical and parenthesized pattern reference](../../language-reference/operators/patterns.md#logical-patterns)
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
static class ListPatterns
{
public static void Run()
{
Console.WriteLine($"Header: {IsHeader(["Name", "Score"])}");
Console.WriteLine(GetAnnouncements(["Mina", "Luis", "Ada"]));
Console.WriteLine(
GetInputFile(["--verbose", "--safe", "report.csv"]));
Console.WriteLine(
$"Has content: {HasContent(["BEGIN", "value", "END"])}");
}

// <ExactListPattern>
static bool IsHeader(string[] columns) =>
columns is ["Name", "Score"];
// </ExactListPattern>

// <CaptureElements>
static string GetAnnouncements(List<string> finishingOrder) =>
finishingOrder switch
{
[var winner, _, var thirdPlace] =>
$"Winner: {winner}; third place: {thirdPlace}",
_ => "A complete three-runner result isn't available"
};
// </CaptureElements>

// <SlicePattern>
static string GetInputFile(string[] arguments) =>
arguments switch
{
["--verbose", .., var fileName] => $"Verbose processing: {fileName}",
[.., var fileName] => $"Processing: {fileName}",
[] => "No input file was provided"
};
// </SlicePattern>

// <SliceSubpattern>
static bool HasContent(string[] entries) =>
entries is ["BEGIN", .. { Length: > 0 }, "END"];
// </SliceSubpattern>
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Overview.Run();
BasicPatterns.Run();
TypePatterns.Run();
PropertyPositionalPatterns.Run();
RelationalLogicalPatterns.Run();
ListPatterns.Run();
Loading
Loading