Summary#
x is <pattern> answers true or false; a switch expression picks the first arm whose pattern matches. The
patterns are C#'s, and they nest:
class Inner { public int Y = 5; }
class Point { public int X; public Inner Inner = new Inner(); }
string Describe(int n) {
var p = new Point { X = n };
int[] digits = new int[] { 1, 2, n };
return $"{n is > 0 and < 10}|{n is 1 or 2}|{n is not 7}"
+ $"|{p is { X: 1, Inner.Y: > 2 }}"
+ $"|{(digits is [1, .. var rest] ? rest.Length : -1)}";
}Signature#
7 · "a" · null · Color.Red // a CONSTANT — equal to it
> 0 · <= 10 // a RELATIONAL test against a constant
Dog · Dog d // a TYPE test, optionally naming the value at that type
var x · _ // match anything, naming it — or not
not P · P and Q · P or Q · (P) // combine patterns
{ X: P, Inner.Y: P } · Dog { … } // a PROPERTY pattern (type optional), `{ }` = "not null"
[P, P, .. var rest, P] // a LIST pattern, at most one slice `..`
(P, P) // a POSITIONAL pattern over a tuple or a DeconstructDescription#
Combining patterns#
and, or and not combine patterns the way &&, || and ! combine conditions, binding in that order
(not tightest), and parentheses group them: n is (> 0 and < 5) or 7. is not null is the everyday one.
They are ordinary words otherwise — a local may still be called not or and.
Property patterns#
p is { X: 1, Y: > 2 } is true when p is not null and each member matches its own pattern. A dotted member
reads through: { Inner.Y: 5 } is { Inner: { Y: 5 } }, so a null Inner fails the test rather than throwing.
{ } on its own is "not null". A type in front tests the type first and reads the members at that type:
o is { Pet: Dog { Barks: true } }.
List patterns#
[1, 2, 3] matches a list or array of exactly those elements; .. stands for any number of elements (at most one
per pattern), so [1, ..] is "starts with 1" and [.., 9] is "ends with 9". .. var rest names the slice — over
an array, as in C#; a List<T> has no range indexer, so naming its slice is refused with the alternatives. [] is
"empty".
Names a pattern binds#
var x, a designation (Dog d, { X: 1 } p) and a slice name (.. var rest) are readable wherever C# says they
are definitely assigned: later in the same condition after &&, in the true branch of a ?:, in an if's body,
in a switch arm's guard and value. A name under not or on one side of an or binds nothing — C# refuses it
(CS8780), and so does Osy#, saying why.
In a switch expression#
Every pattern is a switch arm, and an arm may add a when guard that must also hold:
class Point { public int X; public int Y; }
string Classify(Point p) {
return p switch {
{ X: > 5 } => "far",
{ Y: var y } when y < 0 => "below",
_ => "other",
};
}
int Ends(int[] a) {
return a switch { [] => 0, [var only] => only, [var first, .., var last] => first + last };
}A switch whose arms are only constants, relational tests, types and _ keeps its exhaustiveness check
(switch expression). Once an arm is any other pattern or carries a guard, the compiler cannot prove a
gap, so a switch with no _ that matches nothing throws InvalidOperationException, as C#'s does.
The value is read once per test#
A pattern that tests a value more than once — F() is > 0 and < 10 — needs a value that reading again cannot change:
a variable, a member, an element. Anything else is refused with the one-line fix, var v = F(); v is …, because
C# reads it exactly once.
Everywhere a condition runs#
A pattern lowers to the comparisons it stands for, so it means the same in a function, in the browser, inside an
in-memory LINQ lambda and in a query over stored rows (Item.Where(i => i.Name is "a" or "b") is SQL's IN-shaped
test).
Examples#
class Point { public int X; }
int FarOnes(List<Point> points) => points.Where(p => p is { X: > 5 }).Count();
string Seen(Point? maybe) => maybe is { X: 0 } ? "origin" : maybe is null ? "none" : "elsewhere";See also#
- switch expression — the arms, ordering and the exhaustiveness check
- Testing which class a value is —
is T,as, a cast andOfType<T>() - if / else — where a pattern's names are readable in the body
- LINQ over a local list — LINQ over a list in hand