A pattern tests a value and can extract parts of it (C# specification clause 11). Patterns
are used in is expressions, switch
statements and
switch expressions.
Kinds of pattern
| Pattern | Example | Since | Clause |
|---|---|---|---|
| Declaration | x is string s | 7.0 | 11.2.2 |
| Constant | x is 0, x is null | 7.0 | 11.2.3 |
| Var | x is var y | 7.0 | 11.2.4 |
| Positional | x is (0, 0) | 8.0 | 11.2.5 |
| Property | x is { Length: 0 } | 8.0 | 11.2.6 |
| Discard | _ | 8.0 | 11.2.7 |
| Type | x is string | 9 | 11.2.8 |
| Relational | x is > 0 | 9 | 11.2.9 |
| Logical | x is > 0 and < 10 | 9 | 11.2.10 |
| List | x is [1, 2] | 11 | 11.2.11 |
| Slice | x is [1, ..] | 11 | 11.2.12 |
Logical, property, positional and list patterns are recursive: they contain other patterns. Any pattern can be put in parentheses. A pattern’s variables are definitely assigned only where the pattern is known to have matched.
The is expression
if (input is string s && s.Length > 0) { }
if (input is not null) { }
if (GetValue() is { } value) { } // non-null, gets a variable
x is null and x is not null never call a user-defined
==. A variable from a negated pattern is in scope where the pattern
failed to match, for instance after an early return:
if (input is not string text) return;
Console.WriteLine(text.Length); // text is definitely assigned here
Switch expressions
A switch expression (C# 8, clause 12.12) selects a value by matching patterns.
static string Size(int n) => n switch
{
< 0 => "negative",
0 => "zero",
> 0 and < 100 => "small",
_ when n % 2 == 0 => "big and even",
_ => "big",
};
- Arms are tried in order, and the first match wins. A later arm that an earlier one makes unreachable is an error.
- An arm may have a
whenguard. - The expression has a natural type if the arms have a best common type; otherwise it is target-typed.
- If the arms are not exhaustive, the compiler warns (CS8509), and an unmatched value
throws
SwitchExpressionExceptionat run time. - Unlike a statement, a switch expression comes after its input, and has no
breakorcasekeywords. Each arm is an expression, or athrow.
Declaration and type patterns
A declaration pattern tests the run-time type of a value and, if it matches, assigns it to a new variable (clause 11.2.2). A type pattern does the same without a variable (clause 11.2.8). Both match a non-null value that:
- has an identity conversion to the type;
- derives from or implements the type, through an implicit reference conversion;
- is a nullable value with that underlying type, and has a value; or
- can be boxed or unboxed to the type, if it is not a
ref struct.
They do not consider user-defined or implicit span conversions.
object o = 42;
if (o is int n) Console.WriteLine(n);
static decimal Toll(Vehicle v) => v switch
{
Car => 2.00m,
Truck t => t.Weight * 0.5m,
null => throw new ArgumentNullException(nameof(v)),
_ => 0m,
};
Constant pattern
A constant pattern tests equality with a constant expression: a numeric, string or
character literal, true or false, an enum member, a
const name, or null. An input of type
Span<char> or ReadOnlySpan<char> can be matched
against a constant string.
Relational patterns
<, >, <= and >= compare a value
with a constant of an integer, floating-point, char or enum type. A
relational pattern does not match null, or a value that fails to convert.
Logical patterns
not, and and or combine patterns (clause 11.2.10).
They bind in the order not, then and, then or.
Parentheses change the grouping.
x is not (float or double);
c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');
c is not >= 'a' and <= 'z'; // means (not >= 'a') and <= 'z'; probably not what was meant
The compiler may check patterns of the same precedence in any order.
Property patterns
A property pattern matches a non-null value when each listed property or field matches
its nested pattern (clause 11.2.6). It may name a type, and add a variable. An empty
{ } matches any non-null value.
date is { Year: 2020, Month: 5, Day: 19 or 20 };
shape is Circle { Radius: > 10 } c;
person is { Address.City: "Oslo" }; // extended, C# 10
Positional patterns
A positional pattern calls Deconstruct, or uses the elements of a tuple,
and matches each value against a nested pattern (clause 11.2.5). Names may be given
for tuple elements or Deconstruct parameters.
point is (0, 0);
point is Point2D (> 0, > 0) p;
point is (X: >= 0, Y: >= 0) { Weight: > 0 }; // combined with a property pattern
(groupSize, day) switch
{
(<= 0, _) => throw new ArgumentException(),
(_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,
(>= 5, _) => 12.0m,
_ => 0.0m,
};
Var pattern
var x matches anything, including null, and assigns it to a new variable
of the input’s compile-time type. It is useful for a temporary inside a boolean
expression. var (x, y) is the same as (var x, var y).
Discard pattern
_ matches anything, including null. It can be used in a switch expression
and inside recursive patterns, but cannot be the whole pattern of an is
expression or a switch statement; use var _ there.
List and slice patterns
A list pattern (C# 11, clause 11.2.11) matches each element of an array or list
against a pattern. The type must be countable and indexable. A slice pattern
.. (clause 11.2.12) matches any number of elements, and at most one may
appear in a list pattern.
int[] a = { 1, 2, 3 };
a is [1, 2, 3]; // true
a is [0 or 1, <= 2, >= 3]; // true
a is [1, ..]; // starts with 1
a is [.., 3]; // ends with 3
a is [var first, _, _]; // captures the first element
message is ['a' or 'A', .. var middle, 'a' or 'A'];
A nested slice pattern needs the type to support range access. Since C# 15, extension members can supply the count and indexer.
Closed hierarchies
In C# 15, a switch expression over a
closed
class is exhaustive once it handles every direct subclass that is visible at that
point. A nullable input also needs a null arm. If a direct subclass
is less accessible and hidden from the switch, the compiler warns that the switch is
not exhaustive. A type parameter constrained to a closed class counts in the same way.
Unions
For a union type (C# 15), the pattern’s form decides what it tests:
| Pattern | Applied to |
|---|---|
Type or declaration, such as Dog d | The contents |
Property or positional with a type, such as Dog { Breed: "x" } | The contents, then the nested patterns |
Property or positional with no type, such as { Name: "x" } | The union instance’s own members |
| List pattern | The union instance’s count and indexer |
var or discard | The union instance, without unwrapping |
| Constant or relational | The contents; null also matches a null union reference or nullable wrapper |
A switch is exhaustive once it handles every case type, provided the contents are
not nullable. A default union contains null, so a null arm is the way to
handle it; a discard or var arm would also accept any case type added later.