# Patterns

> A pattern asks a question about a value's SHAPE — is it 7, is it over 0 and under 10, does it have an X of 1, is it a list that starts with 1 — and can name the parts it matched. Write one after `is` or as a switch expression's arm: constants, relational tests, `and`/`or`/`not`, property patterns `{ X: 1, Inner.Y: > 2 }`, list patterns `[1, _, .. var rest]`, positional `(1, var s)`, type tests `Dog d`, `var x` and `_`, exactly as C# writes them.

<!-- id: function-patterns · area: function · stability: stable · html: https://osysharp.com/reference/function/patterns/ -->

## Summary        {#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:

```osy title="the pattern forms in one place" test app=patterns-overview
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      {#signature}
```osy syntax
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 Deconstruct
```

## Description    {#description}

### Combining patterns   {#combinators}
`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   {#property}
`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   {#list}
`[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   {#bindings}
`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   {#switch}
Every pattern is a switch arm, and an arm may add a `when` guard that must also hold:

```osy title="patterns and guards as switch arms" test app=patterns-switch
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](https://osysharp.com/reference/function/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   {#read-once}
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   {#everywhere}
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       {#examples}
```osy title="a pattern inside LINQ, and on a value that may be null" test app=patterns-linq
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       {#see-also}
- [switch expression](https://osysharp.com/reference/function/switch-expression/) — the arms, ordering and the exhaustiveness check
- [Testing which class a value is](https://osysharp.com/reference/class/type-tests/) — `is T`, `as`, a cast and `OfType<T>()`
- [if / else](https://osysharp.com/reference/function/if/) — where a pattern's names are readable in the body
- [LINQ over a local list](https://osysharp.com/reference/query/in-memory-linq/) — LINQ over a list in hand
