C# List Patterns: Match Arrays and Collections
List patterns, introduced in C# 11, extend pattern matching to
sequences such as arrays, lists, and spans. This article covers how list
patterns work, their core syntax elements—including exact matching,
discard patterns, and slice patterns—and practical examples
demonstrating how to match and deconstruct collections efficiently using
is expressions and switch statements.
What Are List Patterns?
List patterns allow you to test whether a collection matches a
specific sequence of elements and structure. They work with any type
that is countable (has a Length or Count
property) and indexable (provides an integer indexer), including
T[], List<T>,
Span<T>, and ReadOnlySpan<T>.
A list pattern is enclosed in square brackets [...]. The
pattern checks both the length of the collection and the values or
patterns of its individual elements.
Basic Sequence and Element Matching
The simplest list pattern matches elements in exact sequence and checks the exact length of the collection:
int[] numbers = { 1, 2, 3 };
if (numbers is [1, 2, 3])
{
Console.WriteLine("Matches exact values and length of 3.");
}If the array has fewer or more elements, the pattern fails.
Discard Pattern (_)
To match an element regardless of its value while enforcing
collection length, use the discard pattern _:
int[] scores = { 10, 42, 30 };
if (scores is [10, _, 30])
{
Console.WriteLine("Starts with 10, ends with 30, and has exactly 3 items.");
}The Slice Pattern (..)
The slice pattern (..) matches zero or more elements. It
allows you to inspect specific positions without restricting the
collection to a fixed size.
Matching Prefixes and Suffixes
int[] values = { 1, 2, 3, 4, 5 };
// Matches any collection that starts with 1
bool startsWithOne = values is [1, ..];
// Matches any collection that ends with 5
bool endsWithFive = values is [.., 5];
// Matches any collection starting with 1 and ending with 5
bool startsOneEndsFive = values is [1, .., 5];Only one slice pattern (..) is permitted within a single
list pattern.
Capturing Slices
You can capture the elements matched by a slice pattern using a variable declaration:
string[] pathSegments = { "api", "v1", "users", "123" };
if (pathSegments is ["api", var version, .. var remaining])
{
Console.WriteLine($"Version: {version}"); // "v1"
Console.WriteLine($"Remaining: {string.Join('/', remaining)}"); // "users/123"
}The captured sub-sequence retains the collection's compatible slice type (for example, an array slice yields an array, while a span slice yields a sub-span).
Combining with Relational and Logical Patterns
Elements inside list patterns can use relational (<,
>, <=, >=) and logical
(and, or, not) patterns:
int[] measurements = { 5, 12, 100 };
if (measurements is [> 0, >= 10 and <= 20, not 0])
{
Console.WriteLine("All three elements satisfy their respective conditions.");
}Practical Application: Command-Line Argument Parsing
List patterns are particularly effective inside switch
expressions for command-line parsing or message handling:
string HandleCommand(string[] args) => args switch
{
["run"] => "Executing default run...",
["run", "--profile", var p] => $"Running with profile: {p}",
["config", "set", var k, var v] => $"Setting {k} to {v}",
["help" or "-h" or "--help"] => "Displaying help...",
[] => "No arguments provided.",
[var cmd, ..] => $"Unknown command: {cmd}"
};This syntax replaces multiple nested length checks, null validations, and index boundary checks with declarative, type-safe constructs evaluated by the compiler.