Skip to content
Draft
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
130 changes: 130 additions & 0 deletions standard/patterns.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
# 11 Patterns and pattern matching

## 11.1 General

Check warning on line 3 in standard/patterns.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/patterns.md#L3

MDC032::Line length 91 > maximum 81

Check warning on line 3 in standard/patterns.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/patterns.md#L3

MDC032::Line length 86 > maximum 81

Check warning on line 4 in standard/patterns.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/patterns.md#L4

MDC032::Line length 94 > maximum 81
A ***pattern*** may be used with the `is` operator ([§12.15.12](expressions.md#121512-the-is-operator)), in a *switch_statement* ([§13.8.3](statements.md#1383-the-switch-statement)), and in a *switch_expression* ([§12.12](expressions.md#1212-switch-expression)) to describe the shape of data against which incoming data is to be compared. Patterns may be nested, with parts of the data being matched against ***sub-patterns***.

A pattern is tested against a value in a number of contexts:

- In a *switch_statement*, the *pattern* of a *switch_label* is tested against the *selector_expression* of the *switch_statement*.
- With an *is-pattern* operator, the *pattern* on the right-hand-side is tested against the expression on the left.

Check warning on line 10 in standard/patterns.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/patterns.md#L10

MDC032::Line length 87 > maximum 81
- In a *switch_expression*, the *pattern* of a *switch_expression_arm* is tested against the expression on the *switch_expression*’s left-hand-side.

Check warning on line 11 in standard/patterns.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/patterns.md#L11

MDC032::Line length 83 > maximum 81
- In nested contexts, the *sub-pattern* is tested against values retrieved from properties, fields, or indexed from other input values, depending on the pattern form.

The value against which a pattern is tested is called the ***pattern input value***.

Check warning on line 15 in standard/patterns.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/patterns.md#L15

MDC032::Line length 90 > maximum 81
A pattern `P` is *subsumed* by set of unguarded patterns `Q` if any input value matched by `P` is matched by one of the members of `Q`.

In a switch statement ([§13.8.3](statements.md#1383-the-switch-statement)), it is an error if a case’s pattern is *subsumed* by the preceding set of *unguarded* ([§13.8.3](statements.md#1383-the-switch-statement)) cases. In a switch expression ([§12.12](expressions.md#1212-switch-expression)), it is an error if a *switch_expression_arm*’s pattern is *subsumed* by the preceding set of *unguarded* *switch_expression_arm*s’ patterns.

A set of patterns is exhaustive if, for every possible input value, some pattern in the set is applicable. When an implementation detects that a set of patterns is not exhaustive, it shall issue a warning.

Check warning on line 20 in standard/patterns.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/patterns.md#L20

MDC032::Line length 84 > maximum 81

## 11.2 Pattern forms

### 11.2.1 General

Check warning on line 24 in standard/patterns.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/patterns.md#L24

MDC032::Line length 91 > maximum 81

A pattern may have one of the following forms:

Expand All @@ -40,6 +40,8 @@
| discard_pattern
| type_pattern
| relational_pattern
| list_pattern
| slice_pattern
;

parenthesized_pattern
Expand Down Expand Up @@ -701,3 +703,131 @@
>
>
> *end example*

### §list-pattern-new-clause List pattern

A *list_pattern* matches a sequence of elements in a list or an array.

```ANTLR
list_pattern
: list_pattern_clause simple_designation?
;

list_pattern_clause
: '[' (pattern (',' pattern)* ','?)? ']'
;
```

A *list_pattern* is compatible with any type that is *countable* ([§18.1](ranges.md#181-general)) as well as *indexable* ([§18.1](ranges.md#181-general))—it has an accessible indexer that takes an `Index` as an argument, or an accessible indexer with a single `int` parameter. If both indexers are present, the former is preferred. (See [§18.4.2](ranges.md#1842-implicit-index-support) for details of implicit index support.)

A pattern of the form `expr is [1, 2, 3]` is equivalent to the following code:

```csharp
expr.Length is 3
&& expr[new Index(0, fromEnd: false)] is 1
&& expr[new Index(1, fromEnd: false)] is 2
&& expr[new Index(2, fromEnd: false)] is 3
```

> *Example*:
>
> <!-- Example: {template:"standalone-console", name:"ListPattern1", expectedOutput:["True", "False", "False", "True"]} -->
> ```csharp
> int[] numbers = { 1, 2, 3 };
>
> Console.WriteLine(numbers is [1, 2, 3]); // True
> Console.WriteLine(numbers is [1, 2, 4]); // False
> Console.WriteLine(numbers is [1, 2, 3, 4]); // False
> Console.WriteLine(numbers is [0 or 1, <= 2, >= 3 and not 7]); // True
> ```
>
> *end example*

The discard pattern ([§11.2.7](patterns.md#1127-discard-pattern)) matches any single element.

> *Example*:
>
> <!-- Example: {template:"standalone-console", name:"ListPattern2", expectedOutput:["The second element is 2."]} -->
> ```csharp
> List<int> numbers = new() { 1, 2, 3 };
>
> if (numbers is [_, var second, _])
> {
> Console.WriteLine($"The second element is {second}.");
> }
> ```
>
> *end example*

### §slice-pattern-new-clause Slice pattern

A *slice_pattern* discards zero or more elements. It shall only be used directly in a *list_pattern_clause*, and then only once at most in that clause.

```ANTLR
slice_pattern
: '..' pattern?
;
```

A *slice_pattern* without a subpattern is compatible with any type that is compatible with a *list_pattern*. A *slice_pattern* with a subpattern is compatible with any type that is *countable* ([§18.1](ranges.md#181-general)) as well as *sliceable* ([§18.1](ranges.md#181-general))—it has an accessible indexer that takes a `Range` as an argument, or an accessible `Slice` method with two `int` parameters. If both are present, the former is preferred. (See [§18.4.2](ranges.md#1842-implicit-index-support) for details of implicit index support.)

A *slice_pattern* acts like a proper discard; that is, no tests shall be made for such pattern. Rather, it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is [1, .. var s, 3]` is equivalent to the following code (if compatible via explicit `Index` and `Range` support):

```csharp
expr.Length is >= 2
&& expr[new Index(0, fromEnd: false)] is 1
&& expr[new Range(new Index(1, fromEnd: false), new Index(1, fromEnd: true))] is var s
&& expr[new Index(1, fromEnd: true)] is 3
```

The input type for a *slice_pattern* is the return type of the underlying `this[Range]` or `Slice` method with two exceptions: For `string`s and arrays, `string.Substring` and `RuntimeHelpers.GetSubArray`, respectively, shall be used.

> *Example*: A slice pattern can be used to match elements only at the start or/and the end of an input sequence.
>
> <!-- Example: {template:"standalone-console", name:"SlicePattern1", expectedOutput:[ "True", "True", "False", "False", "True", "False", "True", "True", "True", "False"]} -->
> ```csharp
> Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]); // True
> Console.WriteLine(new[] { 1, 1 } is [_, _, ..]); // True
> Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]); // False
> Console.WriteLine(new[] { 1 } is [1, 2, ..]); // False
>
> Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]); // True
> Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]); // False
> Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]); // True
>
> Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]); // True
> Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]); // True
> Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]); // False
> ```
>
> *end example*
<!-- markdownlint-disable MD028 -->

<!-- markdownlint-enable MD028 -->
> *Example*: A subpattern can be nested within a slice pattern:
>
> <!-- Example: {template:"standalone-console", name:"SlicePattern2", expectedOutput:["Message aBBA matches; inner part is BB.", "Message apron doesn't match.", "not valid", "valid"]} -->
> ```csharp
> MatchMessage("aBBA"); // output: Message aBBA matches; inner part is BB.
> MatchMessage("apron"); // output: Message apron doesn't match.
>
> void MatchMessage(string message)
> {
> var result = message is ['a' or 'A', .. var s, 'a' or 'A']
> ? $"Message {message} matches; inner part is {s}."
> : $"Message {message} doesn't match.";
> Console.WriteLine(result);
> }
>
> Validate(new[] { -1, 0, 1 }); // output: not valid
> Validate(new[] { -1, 0, 0, 1 }); // output: valid
>
> void Validate(int[] numbers)
> {
> var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0]
> ? "valid" : "not valid";
> Console.WriteLine(result);
> }
> ```
>
> *end example*
Loading