Codectionary / Developer documentation / C#

Pattern Matching

Pattern matching, expanded significantly since C# 7, lets you test a value against a shape or condition using is, switch expressions, and more, often extracting matched data in the same step. It covers type patterns (checking and casting in one step), property patterns (matching on an object's properties), and relational patterns (matching numeric ranges), making conditional logic more expressive and less error-prone than manual casting.

Read the syntax

is tests a pattern and can bind a typed variable for the matching branch. A switch expression selects the first matching arm and returns its result.

Property patterns inspect named members, while relational and logical patterns combine conditions. Order specific arms before broad fallback patterns.

Syntax

if (value is Type variable) { }\nvalue switch { pattern => result }

Examples

Type Pattern with is

Testing and casting in a single step, avoiding a separate explicit cast afterward.

object item = "Hello";

if (item is string s)
{
    Console.WriteLine($"It's a string of length {s.Length}");
}

object number = 42;
if (number is int n && n > 0)
{
    Console.WriteLine($"Positive int: {n}");
}
  1. Testing and casting in a single step, avoiding a separate explicit cast afterward.
  2. is tests a pattern and can bind a typed variable for the matching branch. A switch expression selects the first matching arm and returns its result.
  3. The first pattern binds Hello as string s and prints its length 5. The second binds 42 as int n and prints the positive-int message.

Property Patterns

Matching based on the properties of an object, useful for destructuring records and classes.

Point p = new Point(0, 5);

string description = p switch
{
    { X: 0, Y: 0 } => "Origin",
    { X: 0 } => "On the Y axis",
    { Y: 0 } => "On the X axis",
    _ => "Somewhere else"
};

Console.WriteLine(description);  // On the Y axis

record Point(int X, int Y);
  1. Matching based on the properties of an object, useful for destructuring records and classes.
  2. is tests a pattern and can bind a typed variable for the matching branch. A switch expression selects the first matching arm and returns its result.
  3. Point has X equal to 0 and Y equal to 5, so the second property pattern matches. The description prints On the Y axis.

Relational and Logical Patterns

Matching numeric ranges and combining patterns with and/or/not.

string Classify(int age) => age switch
{
    < 0 => "Invalid",
    >= 0 and < 13 => "Child",
    >= 13 and < 20 => "Teenager",
    >= 20 => "Adult"
};

Console.WriteLine(Classify(15));  // Teenager

object value = 5;
if (value is not null and int)
{
    Console.WriteLine("It's a non-null int");
}
  1. Matching numeric ranges and combining patterns with and/or/not.
  2. is tests a pattern and can bind a typed variable for the matching branch. A switch expression selects the first matching arm and returns its result.
  3. The age 15 matches the teenager range. The separate non-null int pattern also succeeds for value 5 and prints its message.

Pattern Matching in switch Statements

Pattern matching also works in traditional switch statements, not just switch expressions.

void Describe(object obj)
{
    switch (obj)
    {
        case int n when n > 0:
            Console.WriteLine($"Positive: {n}");
            break;
        case string s:
            Console.WriteLine($"String: {s}");
            break;
        case null:
            Console.WriteLine("Nothing here");
            break;
        default:
            Console.WriteLine("Unknown type");
            break;
    }
}

Describe(42);
Describe("hi");
  1. Pattern matching also works in traditional switch statements, not just switch expressions.
  2. is tests a pattern and can bind a typed variable for the matching branch. A switch expression selects the first matching arm and returns its result.
  3. Describe(42) matches the guarded positive-int case; Describe("hi") matches the string case. Each call prints its corresponding description.

Best practices

  • Use "is Type variable" instead of a separate "is Type" check followed by an explicit cast - it combines both steps and avoids duplicating the type name
  • Use property patterns to destructure and match on object shape directly in a switch expression, instead of a chain of separate if statements
  • Combine relational patterns (< 0, >= 20) with switch expressions for clean, readable range-based branching
  • Prefer pattern matching over manual "is" + cast + null checks scattered across multiple lines - it consolidates the logic and reduces the chance of a mismatched check

Runtime & versions

.NET runtime. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

C# language reference

Specifications & further reading