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.

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}");
}

Property Patterns

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

record Point(int X, int Y);

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

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");
}

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");

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

At a glance

Purpose
Applications on the .NET platform
File extension
.cs
Runs in
.NET runtime
Usually used with
.NET SDK and libraries

Specifications & further reading

Related C# documentation

Delegates
A delegate is a type-safe reference to a method - essentially a variable that can hold a method and be invoked like one. Delegates enable passing behavior around as data, forming the foundation for events, callbacks, and LINQ. C# provides built-in generic delegate types, Func<> and Action<>, that cover the vast majority of use cases without needing to declare a custom delegate type.
Events
An event is a special kind of delegate field that provides a publish-subscribe pattern: the declaring class can raise (invoke) the event, but external code can only subscribe (+=) or unsubscribe (-=) - it cannot invoke the event directly or overwrite existing subscribers. This makes events the standard, safe way to notify other parts of a program that something has happened, like a button being clicked or a value changing.
Extension Methods
Extension methods let you add new methods to an existing type - including types you don't own, like built-in .NET types or third-party classes - without modifying its source code or creating a subclass. They're defined as static methods in a static class, with the first parameter prefixed with this to indicate which type they extend. LINQ's Where(), Select(), and friends are themselves implemented as extension methods on IEnumerable<T>.
Nullable Reference Types & Null-Coalescing
Since C# 8, nullable reference types let the compiler track and warn about potential null reference issues at compile time, even for reference types like string and custom classes (which were always implicitly nullable before). A type like string means 'never null' under this feature, while string? explicitly allows null. The null-coalescing operator (??) and null-conditional operator (?.) provide concise syntax for safely working with potentially-null values.