Skip to content

Pattern Matching in C#

Overview

Pattern matching in C# allows you to check a value against a pattern and, when it matches, extract information from that value in a concise and readable way.

It extends the power of traditional conditionals (if, switch) by combining:

  • Type checking
  • null checking
  • value deconstruction

into one clean syntax.

In simple terms:

Pattern matching lets you say “if this object fits this shape, use it” — in a single, readable expression.

Core Concept Example

Traditional approach

if (obj is Customer)
{
    var c = (Customer)obj;
    Console.WriteLine(c.Name);
}

Pattern matching approach

if (obj is Customer c)
{
    Console.WriteLine(c.Name);
}
This is:

  • More concise
  • Type-safe — no need for a cast
  • Readable and expressive

Types of Patterns in C#

1. Type Patterns

Used to check type and extract the object.

if (shape is Circle c)
    Console.WriteLine($"Radius: {c.Radius}");

2. Constant Patterns

Check if a value equals a constant.

if (statusCode is 200)
    Console.WriteLine("OK");
else if (statusCode is 404)
    Console.WriteLine("Not Found");

3. Relational Patterns

Compare values using relational operators (<, >, <=, >=).

if (temperature is > 30)
    Console.WriteLine("Too hot!");

4. Logical Patterns (and, or, not)

Combine multiple patterns logically.

if (age is >= 18 and < 65)
    Console.WriteLine("Working-age adult");

if (value is not null)
    Console.WriteLine("Value is set");

5. Property Patterns

Match object properties directly.

if (person is { Name: "Alice", Age: >= 18 })
    Console.WriteLine("Adult named Alice");

You can also nest patterns:

if (order is { Customer.Address.Country: "UK" })
    Console.WriteLine("British order");

6. Tuple and Positional Patterns

Match tuples or deconstructed objects.

(string country, int population) = ("UK", 68);

if ((country, population) is ("UK", > 60))
    Console.WriteLine("Large European country");

With deconstruction support:

if (point is (0, 0))
    Console.WriteLine("Origin");

7. Switch Expressions with Patterns (C# 8+)

The modern switch syntax is built around pattern matching:

string GetWeatherAdvice(int temperature) => temperature switch
{
    < 0   => "Freezing",
    < 15  => "Cold",
    < 25  => "Mild",
    < 35  => "Warm",
    _     => "Hot"
};

This is:

  • Cleaner than nested ifs
  • More readable
  • Easy to extend

Why Pattern Matching Is Good

Benefit Description
Readability Replaces verbose type-checking and casting logic.
Safety Eliminates runtime casting errors — compiler ensures correctness.
Conciseness Fewer lines of code and reduced boilerplate.
Declarative style Focuses on what you’re checking, not how.
Better maintainability Easy to extend complex branching logic.

Real-World Example: Message Handling

object message = new OrderPlaced("1234");

switch (message)
{
    case OrderPlaced o:
        ProcessOrder(o.Id);
        break;

    case OrderCancelled c:
        CancelOrder(c.Id);
        break;

    default:
        LogUnknown(message);
        break;
}

Here, pattern matching cleanly replaces multiple if/is checks — common in event-driven systems.

Drawbacks and Considerations

Drawback Explanation
Overuse can reduce clarity Deeply nested property patterns can become hard to read.
Performance overhead Each match can introduce small runtime checks (usually negligible).
Complex switch expressions Large pattern-based switches may be harder for new developers to follow.
Not ideal for dynamic types Pattern matching relies on static typing — dynamic objects need extra handling.

Example Summary (Compact Form)

public string Describe(object o) => o switch
{
    int i        => $"Integer: {i}",
    string s     => $"String: '{s}'",
    null         => "Null value",
    { } obj      => $"Some object of type {obj.GetType().Name}",
    _            => "Unknown"
};

Summary

Pattern matching in C# allows you to check a variable’s type, value, or structure and extract data in one expression. It’s used to simplify type checks, null checks, and conditional logic — making code more readable and type-safe. It’s especially powerful in modern switch expressions and with records or deconstructed tuples. It should be used to simplify code, but avoided when patterns become overly complex or nested.