Skip to content
Merged
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
68 changes: 68 additions & 0 deletions docs/csharp/fundamentals/patterns/list-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: "List and slice patterns"
description: Learn how C# list patterns describe a sequence's shape through its element count, ordered positions, matched values, and slices.
ms.date: 09/24/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 sequence's *shape* is the set of observable properties that a pattern requires. These properties can include the number of elements, requirements written for every element or for a sequence of elements as a whole, and marker values at specific indexes. For example, a shape might require exactly two elements with `"Name"` at index 0, or it might require a first marker, a last marker, and a nonempty sequence between them.

A *list pattern* describes a shape by combining an element-count requirement with nested patterns at ordered positions. It tests only the requirements written in the pattern: An element subpattern tests its corresponding element, while a *slice pattern* can allow or test a sequence of otherwise unmatched elements. You can use these parts to match an exact count and marker values, capture values at fixed positions, allow an unmatched middle, or apply a nested pattern to that middle.

Choose a list pattern when the input's compile-time type provides a `Length` or `Count` property and indexed element access, which retrieves an element by its position, as in `input[index]`. Arrays, `List<T>`, strings, and spans are common examples. The compiler determines eligibility from the variable's declared, compile-time type because it must resolve these required members before the program runs. The <xref:System.Collections.Generic.IEnumerable%601> interface by itself provides enumeration instead of those members, even when the runtime object is an indexable collection.

List patterns use a length or count and indexed element access instead of enumeration. For common built-in types, matching can check only the positions named by the pattern rather than iterate through every element. The cost of those operations depends on the input type. Iterating a long collection can take time, so use a loop or LINQ when the decision requires examining an arbitrary number of elements rather than specific positions.

## 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. An exact list pattern omits a slice pattern, requires exactly two elements, and applies each nested pattern to the element at the same index. This pattern describes a shape with an exact count and two marker values: `"Name"` at index 0 and `"Score"` at index 1. Nested element patterns are optional: The empty list pattern `[]` describes, and matches, a sequence with no elements.

Choose a list pattern when the shape combines an element count with requirements at ordered positions. If only the number of elements matters, a `Length` or `Count` property pattern, such as `items is { Count: 0 }`, states that intent more directly.

## Use a discard to match any element value

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":::

The `var winner` and `var thirdPlace` patterns each declare a variable. When the list pattern matches, C# assigns the matched element value at that position to the corresponding variable, so the result can use `winner` and `thirdPlace`. The discard pattern `_` accepts the second element and discards its value. This shape requires exactly three elements and gives the first and third positions specific meaning, but it doesn't require particular values at any position.

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

In this simplified example, a command line can start with `--verbose` and contain other arguments, and is assumed to end with the input file name. The following method recognizes shapes with a file name in the last position and captures that value:

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

The *slice pattern* `..` allows zero or more remaining elements between the first and last element patterns. A list pattern can contain at most one slice pattern. The first switch arm describes a shape with at least two elements: the marker `"--verbose"` at index 0, a file name in the last position, and any number of unmatched arguments between them. The standalone slice doesn't test those middle arguments. The second arm accepts any nonempty shape and captures its last element, while the empty pattern handles a sequence with no elements.

A slice can appear at the beginning, middle, or end of a list pattern. Use it when a variable-length sequence is part of the shape but only the surrounding positions need element tests. Use 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 the marker `"BEGIN"` at index 0 and `"END"` at the last index. The property pattern `{ Length: > 0 }` then adds a requirement for the sequence matched by the slice: It must contain at least one element. Together, these requirements describe the complete shape. The pattern doesn't test the values of the elements inside the slice.

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)
17 changes: 11 additions & 6 deletions docs/csharp/fundamentals/patterns/pattern-matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ You can use a pattern in three contexts:
- In a `case` label of a `switch` statement.
- In an arm of a `switch` expression.

Patterns are often clearer than a sequence of comparison statements because each branch describes the data it handles. For example, the following method uses a `switch` expression to choose a delivery message:
Patterns test one evaluated input against types, constants, or a shape that can contain nested patterns. A Boolean condition can also compare two independently evaluated expressions whose values aren't constants. When either form can express the same test, choose the form that's easier to read.

For example, the following method uses a `switch` expression to choose a delivery message:

:::code language="csharp" source="snippets/patterns/Overview.cs" ID="SwitchExpressionOverview":::

Recursive patterns have their own input expressions. In `StandardDelivery { Days: <= 2 }`, the outer pattern receives the `delivery` expression. The recursive `<= 2` pattern receives the `Days` property expression from the matched `StandardDelivery` object.
Property patterns can include an outer type test and nested patterns, but neither is required in every property pattern. In `StandardDelivery { Days: <= 2 }`, the outer pattern receives the `delivery` expression and tests its type. The `Days` property expression then becomes the input to the nested relational pattern `<= 2`.

The expression before `switch` is the input expression. Each line inside the braces is a *switch arm*. The pattern appears before `=>`, and the result appears after it. C# evaluates the input expression, then selects the first arm, in text order, whose pattern matches and whose optional `when` guard is `true`. The optional `when` guard is an additional Boolean condition written after the pattern. The preceding example showed the following patterns:

Expand Down Expand Up @@ -79,17 +81,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,78 @@
---
title: "Property and positional patterns"
description: Learn when to use C# property patterns to test named members and positional patterns to test ordered values.
ms.date: 09/24/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 both test parts of a value. The difference is how they identify those parts:

- A *property pattern* names the properties or fields to test.
- A *positional pattern* identifies values by their order.

A *deconstruction* exposes an ordered set of component values. A tuple already has an element order; see [deconstruct tuples](../types/tuples.md#deconstruct-tuples). For another type, a [`Deconstruct` method](../functional/deconstruct.md#user-defined-types) defines which component values are exposed and their order.

## Compare names and positions

The following property pattern 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 following positional pattern tests a signal value followed by a Boolean value:

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

The property pattern identifies its inputs by the names `TemperatureC` and `HumidityPercent`. The crossing code creates a tuple from the separate `signal` and `crossingIsClear` values. The tuple pattern then identifies those values by order: `signal` first and `crossingIsClear` second. A positional pattern is a strong fit because this newly created tuple has only two values, and their order has a clear meaning in the crossing decision.

Choose a property pattern when member names help explain the test. Property patterns are usually clearer for classes, structs, and records. Choose a positional pattern when order already gives the values an obvious meaning. Positional patterns are most useful with tuples, which combine multiple related values into one value with a fixed order.

## Follow nested inputs in recursive patterns

Property and positional patterns are *recursive patterns*: They apply another pattern to each property, field, or position they select. The selected value becomes the input to that nested pattern.

In `IsHotAndHumid`, the `reading` expression is the input to the property pattern. C# evaluates that expression before matching. The pattern gets two values from the resulting object:

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

An outer type test is optional, and recursive pattern clauses can be empty. For example, the empty property pattern `{ }` matches any non-null evaluated value.

Property and positional patterns match only non-null evaluated values. When `null` is part of the input domain, choose a recursive pattern that checks for a non-null value first:

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

The input expression is `value`. C# evaluates it, and the `{ }` property pattern tests the resulting value for non-null before assigning it to `nonNullValue`. The following switch expression can then test multiple possible runtime types. Its `DateTime` and `string` type patterns have no designation because the method only needs to identify each type, not capture its value.

When recursive pattern clauses contain nested patterns, each selected property, field, or position becomes the input to its nested pattern.

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":::

`value` is the input expression. C# first evaluates it and tests whether the resulting value is a <xref:System.DateTime>. The `Date` property value then becomes the input for the `DayOfWeek` member access. Finally, the `DayOfWeek` value becomes the input to the logical pattern that tests two constants. Matching succeeds when the outer value has the specified type and every object needed along the member path is non-null.

## Compare patterns with branching statements

The earlier `DescribeDate` method expresses four results as patterns:

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

The following method produces the same results with a series of imperative branching statements:

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

The pattern-based version keeps the possible results together when several branches test a value's type and shape. The imperative version makes each test and return step explicit. For one condition, either form might look similar. As the number of related branches grows, patterns can make the alternatives easier to compare.

## 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).
Loading
Loading