Skip to main content
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.

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.

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.

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.
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.

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.

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.
Tired of Poor C# Skills? Fix That With Deep Grasping!Learn More