Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

List patterns in C# provide a declarative syntax to match sequences of elements against a series of nested positional and structural patterns. Introduced in C# 11, they enable pattern matching on collections by evaluating the sequence’s length and the individual elements at specific indices.

Supported Types

List patterns can be applied to any type that is countable and indexable. The compiler requires the target type to expose:
  • An accessible Length or Count property (to evaluate sequence length).
  • An accessible indexer taking an int (this[int]) or a System.Index (this[System.Index]) argument (to evaluate individual elements).
Types natively satisfying these requirements include arrays (T[]), System.Collections.Generic.List<T>, Span<T>, and ReadOnlySpan<T>.

Syntax and Mechanics

The list pattern is defined using square brackets [...] containing a comma-separated list of subpatterns. The target sequence must match the pattern’s structural requirements (length) and all individual subpatterns.

1. Exact Sequence Matching

Matches a sequence of an exact length where each element satisfies the corresponding positional pattern.
int[] numbers = { 1, 2, 3 };

// Matches exactly three elements: 1, 2, and 3.
bool isExactMatch = numbers is [1, 2, 3]; 

// Matches exactly three elements where the second element satisfies a relational pattern.
bool isRelationalMatch = numbers is [1, > 1, 3];

2. The Discard Pattern (_)

The discard pattern matches exactly one element of any value at a specific position. It enforces the length constraint without evaluating the element’s content.
int[] numbers = { 1, 2, 3 };

// Matches any three-element sequence starting with 1 and ending with 3.
bool isMatch = numbers is [1, _, 3];

3. The Slice Pattern (..)

The slice pattern matches zero or more elements. It is used to match sequences of arbitrary lengths. A list pattern can contain at most one slice pattern.
int[] numbers = { 1, 2, 3, 4, 5 };

// Matches any sequence starting with 1 and ending with 5.
bool isMatch1 = numbers is [1, .., 5];

// Matches any sequence starting with 1 and 2, regardless of total length.
bool isMatch2 = numbers is [1, 2, ..];

// Matches any sequence of length >= 2 ending with 4 and 5.
bool isMatch3 = numbers is [.., 4, 5];

4. Capturing Elements and Slices

Individual elements or entire slices can be captured into new local variables using the var pattern or explicit type patterns. To capture a slice (e.g., .. var slice), the target type must be sliceable. A type is sliceable if it satisfies any of the following conditions:
  • It has an accessible indexer that takes a System.Range argument.
  • It has an accessible Slice(int, int) method.
  • It has intrinsic compiler support for slicing, specifically arrays (T[]) and string types.
int[] numbers = { 1, 2, 3, 4, 5 };

// Captures the second element into the variable 'second'.
if (numbers is [1, var second, ..])
{
    // 'second' is 2
}

// Captures the middle elements into a new sequence variable 'middle'.
if (numbers is [1, .. var middle, 5])
{
    // 'middle' is of type int[] containing { 2, 3, 4 }.
}
Important: While System.Collections.Generic.List<T> supports general list patterns, it is not sliceable because it lacks a Range indexer, a Slice(int, int) method, and intrinsic compiler slicing support. Attempting to capture a slice from a List<T> will result in compiler error CS8906.
using System.Collections.Generic;

List<int> list = new() { 1, 2, 3, 4 };

// Valid: Matches length and elements without capturing a slice.
bool isValid = list is [1, .., 4]; 

// Invalid: CS8906 - List<int> does not support slicing.
// bool isInvalid = list is [1, .. var middle, 4]; 

5. Nested and Logical Patterns

List patterns support deep nesting, allowing the use of logical patterns (and, or, not), property patterns ({ ... }), and type patterns within the sequence definition.
string[] strings = { "apple", "banana", "cherry" };

// Matches a 3-element array where the first element is "apple",
// the second element's length is > 5, and the third is not null.
bool isComplexMatch = strings is ["apple", { Length: > 5 }, not null];

object[] objects = { 1, "two", 3.0 };

// Matches a 3-element array based on type patterns, capturing the string.
bool isTypeMatch = objects is [int, string s, double];

Compiler Lowering Behavior

When a list pattern is compiled, the C# compiler translates the pattern into a series of optimal null checks, bounds checks, and indexer calls. Pattern matching is inherently null-safe. For example, the pattern numbers is [1, 2, ..] is lowered to an implicit null check followed by a length check and element evaluations:
  1. numbers != null
  2. numbers.Length >= 2
  3. numbers[0] == 1
  4. numbers[1] == 2
When capturing slices (.. var slice), the compiler utilizes the System.Range struct. If the target type is an array, it allocates a new array for the slice using System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray. If the target is a Span<T> or ReadOnlySpan<T>, it performs a zero-allocation Slice operation.
Master C# with Deep Grasping Methodology!Learn More