Skip to content
Theory · Enums, records and patterns · lesson 6 of 8

Patterns with is

Think of a sorting machine in a post office warehouse. It has slots of different shapes: only flat envelopes pass through one, only boxes under a kilogram through another. Each slot asks a parcel one question: "Do you fit?" The answer is yes or no.

In the theme on inheritance, you already wrote v is Truck t: a type check and a new variable in one step. The code Truck t after is is a pattern, that very slot: a description of what a value must look like. Checking a value against a pattern is called pattern matching. In is, the value goes on the left, the pattern on the right, and the result is true or false.

Patterns come in other kinds too, but let's start with the familiar one. A pattern like Truck t also works with a number boxed in an object:

C#
object box = 42;

if (box is int n)
{
    Console.WriteLine(n + 1); // 43
}

if (box is string s)
{
    Console.WriteLine(s.ToUpper());
}

Let's take the program apart line by line:

  1. object box = 42; puts a number into a variable of type object, which can hold a value of any type.
  2. box is int n checks whether box holds a number of type int. It does, so the expression gives true, and the number goes into a new variable n of type int. This is unboxing with a check: no (int)box cast and no risk of an exception.
  3. n + 1 adds ordinary numbers, and the program prints 43. You can't do that with box itself: box + 1 doesn't compile, because object has no addition.
  4. box is string s gives false: box doesn't hold a string. The body of the second if doesn't run, and the program prints nothing more.

The type pattern

The code int n after is is called a type pattern. Like Truck t, it does two things in one step:

  • checks that the value has type int;
  • if it does, puts the value into a new variable n, already typed as int.

Without the pattern, you would need a check and a separate cast: if (box is int) { int k = (int)box; … }. You would have to write the type twice.

As you already know, a type pattern never matches null: if box holds null, the check box is string s gives false. So inside the if, the variable from the pattern is never null.

This is where the slot analogy breaks down. A slot only lets the parcel through, but the pattern int n also hands you the value in a new variable.

Checking for null

A pattern can also be a specific value. The most common one is null:

C#
string? name = null;
Console.WriteLine(name is null);     // True
Console.WriteLine(name is not null); // False

is null checks that the variable holds no value. The word not reverses a pattern, so is not null gives true when there is a value.

The check name == null would give the same here. But is null always checks for exactly null, while a type can define the == operator its own way, as a record does. That is why is is the more common choice for null checks.

Relational patterns

A relational pattern compares a value with a number using <, <=, > and >=. Patterns are combined with the words and and or, and reversed with not:

C#
int age = 15;
Console.WriteLine(age is >= 13 and <= 19); // True
Console.WriteLine(age is < 0 or > 120);    // False
Console.WriteLine(age is not 15);          // False

What each word does:

  • and—the value must match both patterns;
  • or—at least one of them;
  • not—the value must not match the pattern.

and and or combine patterns for one and the same value, so you write age only once. The code age is >= 13 && <= 19 doesn't compile: && joins two complete conditions, and <= 19 is not a condition by itself.

The right side of a comparison is a number or a constant, not an ordinary variable. For age is > limit, the compiler answers with error CS9135: it needs a value known at compile time.

Common mistake

The variable from a pattern was used after the if:

C#
object box = 42;
if (box is int n)
{
    Console.WriteLine("int");
}
Console.WriteLine(n); // ✗
TEXT
error CS0165: Use of unassigned local variable 'n'

The program doesn't compile. The variable n gets a value only when the check succeeds, and after the if the compiler doesn't know whether it did. Use n inside the if: it always has a value there.

Key points

  • A pattern describes what a value must look like, and an is check with a pattern gives true or false.
  • box is int n checks the type and puts the value into n at once, and you can use n only where the check surely succeeded.
  • is null and is not null check for exactly null, and a type pattern never matches null.
  • Relational patterns are combined with and and or and reversed with not: age is >= 13 and <= 19.