100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

Pattern Matching

Survey C#'s pattern matching features — type patterns, property patterns, relational and logical patterns, and switch expressions — for expressive, safe branching.

Async & Modern C#Intermediate9 min readJul 9, 2026
Analogies

Pattern Matching

Pattern matching lets you test a value against a shape or condition and, if it matches, extract data from it — all in a single expression, rather than a series of separate type checks and casts. Starting with the is expression in C# 7 and expanding significantly through C# 8-11, C# now supports type patterns, constant patterns, property patterns, positional (deconstruction) patterns, relational patterns, and combinators (and, or, not) that can be composed together. This turns what used to be verbose chains of if/else if with casts into declarative, exhaustively-checkable expressions.

🏏

Cricket analogy: Old-style dismissal checking meant separate 'is it bowled? caught? lbw?' checks with casts; pattern matching is like a single review that tests shape and extracts the outcome (type, constant, property, positional patterns) at once, combining 'caught and not no-ball' with and/or/not.

Type patterns and the is expression

The simplest form, x is Foo f, tests whether x is of type Foo (or a derived type) and, if so, assigns it to a new variable f scoped to the surrounding code — this pattern variable is definitely-assigned only where the compiler can prove the check succeeded, such as inside the true-branch of an if or after a guard clause with a negated check followed by a return/continue.

🏏

Cricket analogy: 'delivery is NoBall nb' is like an umpire checking if a delivery counts as a no-ball and, only if so, handing the scorer a labeled note nb to record the free hit - that note only exists in the branch where the check passed.

csharp
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;
public record Triangle(double Base, double Height) : Shape;

public static class ShapeCalculator
{
    public static double Area(Shape shape) => shape switch
    {
        Circle { Radius: <= 0 } => throw new ArgumentException("Radius must be positive"),
        Circle c => Math.PI * c.Radius * c.Radius,
        Rectangle { Width: var w, Height: var h } => w * h,
        Triangle(var b, var h) => 0.5 * b * h,
        null => throw new ArgumentNullException(nameof(shape)),
        _ => throw new NotSupportedException($"Unknown shape: {shape.GetType().Name}")
    };

    public static string Classify(Shape shape) => shape switch
    {
        Circle or Rectangle { Width: var w, Height: var h } and not { Width: var w2 } when w == h => "regular",
        _ => "irregular"
    };
}

Property, positional, and relational patterns

Property patterns ({ Radius: <= 0 }) match against one or more properties of an object, optionally nesting further patterns inside. Positional patterns (Triangle(var b, var h)) rely on a type's Deconstruct method — automatically synthesized for records — to match and bind multiple values at once. Relational patterns (<= 0, > 100) test numeric or comparable values directly inside a pattern, and logical pattern combinators and, or, and not let you compose these building blocks, e.g. >= 0 and <= 100 for a range check.

🏏

Cricket analogy: '{ Overs: <= 0 }' flags an invalid spell length; a positional pattern like Partnership(var runs, var balls) deconstructs a stand into components, while a relational check '>= 0 and <= 6' validates a legal runs-per-ball range.

switch expressions vs switch statements

A switch expression (introduced C# 8) evaluates to a value using => arms and no break statements, is more concise, and — crucially — the compiler warns if it isn't exhaustive over a closed type hierarchy (like a sealed record hierarchy), catching missing cases at compile time. A traditional switch statement remains useful for executing multiple statements per case or when no single result value is being produced.

🏏

Cricket analogy: A switch expression 'dismissal switch { Bowled => ..., Caught => ..., Lbw => ... }' evaluates to a value with no break statements, and the compiler warns if a sealed dismissal hierarchy is missing a case; a traditional switch is better for logging multiple actions per dismissal.

Pattern matching in C# is intentionally similar in spirit to pattern matching in functional languages like F# or Rust's match, bringing some of that exhaustiveness-checking and destructuring ergonomics to an object-oriented language — records with positional deconstruction make this especially natural since C# 9.

A switch expression without a final _ discard pattern (or that isn't provably exhaustive) throws a runtime InvalidOperationException if no arm matches — the compiler only warns about non-exhaustiveness, it doesn't prevent compilation, so an unhandled case can still surface as a runtime crash rather than a build error.

  • is expressions and switch expressions/statements support type, constant, property, positional, and relational patterns.
  • Property patterns like { Radius: <= 0 } match and can nest against object properties.
  • Positional patterns rely on Deconstruct, which records synthesize automatically.
  • Logical pattern combinators and, or, not compose simpler patterns into richer conditions.
  • switch expressions are exhaustiveness-checked by the compiler (as warnings) over closed hierarchies, unlike switch statements.
  • A non-exhaustive switch expression throws InvalidOperationException at runtime if no pattern matches.

Practice what you learned

Was this page helpful?

Topics covered

#CStudyNotes#Programming#PatternMatching#Pattern#Matching#Type#Patterns#StudyNotes#SkillVeris#ExamPrep