iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Pattern Matching

C# pattern matching switches on shape, not just value. Type patterns, property patterns, relational, list patterns — a declarative way to deconstruct and branch.

Type, property, list, relational, switch expressions

EXAMPLE
using System;
using System.Collections.Generic;

// 1) Type pattern + 'is'
object obj = 42;
if (obj is int n) Console.WriteLine(n * 2);      // n is captured if type matches

// Negated
if (obj is not string s) {
    // not a string
}

// 2) Switch expression — the modern shape
string Describe(object o) => o switch
{
    null              => "null",
    int i when i < 0  => $"negative {i}",
    int i             => $"int {i}",
    string s          => $"string '{s}'",
    int[] a           => $"int[{a.Length}]",
    _                 => $"unknown ({o.GetType().Name})",
};

Console.WriteLine(Describe(42));            // int 42
Console.WriteLine(Describe("hi"));          // string 'hi'
Console.WriteLine(Describe(null));          // null

// 3) Property patterns — match by shape
record Point(int X, int Y);
record Rect(Point TopLeft, Point BottomRight);

string Quadrant(Point p) => p switch
{
    { X: > 0, Y: > 0 } => "I",
    { X: < 0, Y: > 0 } => "II",
    { X: < 0, Y: < 0 } => "III",
    { X: > 0, Y: < 0 } => "IV",
    { X: 0,   Y: 0 }   => "origin",
    _                  => "axis",
};

// 4) Recursive property pattern — drill into nested objects
int AreaOf(Rect r) => r switch
{
    { TopLeft: { X: var tx, Y: var ty }, BottomRight: { X: var bx, Y: var by } }
        => Math.Abs((bx - tx) * (by - ty)),
};

// 5) Deconstruction patterns — for records / types with Deconstruct
string Name(Point p) => p switch
{
    (0, 0)         => "origin",
    (var x, 0)     => $"on X at {x}",
    (0, var y)     => $"on Y at {y}",
    (var x, var y) => $"({x}, {y})",
};

// 6) Discriminated-union style with sealed records
abstract record Shape;
record Circle(double Radius)                : Shape;
record Rectangle(double Width, double Height): Shape;
record Triangle(double Base, double Height)  : Shape;

double Area(Shape s) => s switch
{
    Circle    { Radius: var r }                  => Math.PI * r * r,
    Rectangle { Width:  var w, Height: var h }   => w * h,
    Triangle  { Base:   var b, Height: var h }   => 0.5 * b * h,
    _ => throw new ArgumentException(),
};

// 7) Relational + logical patterns (C# 9+)
string TrafficLight(int speed) => speed switch
{
    < 0            => "reverse",
    0              => "stopped",
    > 0 and <= 30  => "slow",
    > 30 and <= 80 => "normal",
    > 80 and < 130 => "fast",
    _              => "too fast",
};

// 8) List patterns (C# 11+)
string Describe<T>(T[] arr) => arr switch
{
    []          => "empty",
    [var x]     => $"one: {x}",
    [var x, var y] => $"two: {x}, {y}",
    [var first, .., var last] => $"first={first}, last={last}",
    _ => $"len={arr.Length}",
};

Describe(new[] { 1, 2, 3, 4, 5 });      // first=1, last=5

// 9) Patterns inside if / when
void Handle(object e)
{
    if (e is Exception ex && ex.InnerException is { Message: var inner })
        Console.WriteLine($"inner: {inner}");

    if (e is HttpResponseMessage { StatusCode: var sc } and not { IsSuccessStatusCode: true })
        throw new InvalidOperationException($"failed {sc}");
}

// 10) Pattern matching vs visitor pattern
// Old OO: a Visitor<TResult> interface + Accept() method on every variant.
// Modern C#: sealed records + switch expression. Exhaustive (with sealed hierarchies).
// Adding a new case: switch warns about missing arm; compiler enforces.

// 11) Exhaustiveness — compiler help
// With sealed records and a switch expression, the compiler warns if you miss a case.
// Without `_`, missing variants give CS8509.

// 12) Performance
// Switch expressions on patterns compile to efficient code — usually equivalent to
// hand-written if/else chains. The compiler optimises type tests with vtable / jump tables.

Why it matters

Sealed records + switch expressions give you the same benefits as Rust enums / TypeScript discriminated unions — exhaustiveness, no boilerplate visitor pattern, and an inviting site to add behavior over time.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
object o = 42;
string r = o switch {
    int n when n > 0 => "positive",
    int    => "non-positive int",
    string => "a string",
    _      => "other"
};
Try it Yourself »

Discussion

Loading…