What is pattern matching in C#?
Quick Answer
Pattern matching tests a value against a shape and (optionally) extracts data from it in one expression. It covers type patterns (o is Customer c), constant patterns, relational (> 100), logical (and/or/not), property patterns ({ Status: Active }), list patterns and tuple patterns — usable in is, switch statements and switch expressions.
Pattern matching lets me check what a value is and pull data out of it in a single expression, instead of a cast plus a null check plus an if. The common ones are the type pattern — o is Customer c gives me a typed c if it matches — and the switch expression, which returns a value and gets compiler warnings if I miss a case. There are also property patterns like { Status: Active, Total: > 1000 }, and relational and logical patterns with and, or, not. It makes branching code much cleaner, though for genuinely polymorphic behaviour a virtual method can still be the better choice.
It replaces long if/else chains and casts with declarative code:
- Type + declaration:
if (shape is Circle c) return Math.PI * c.R * c.R; - switch expression: returns a value, is exhaustive-checked by the compiler.
- Property pattern:
order is { Total: > 1000, Customer.IsVip: true }. - Relational + logical:
temp switch { < 0 => "freezing", >= 0 and < 30 => "mild", _ => "hot" }. whenclause adds an arbitrary condition to a case.
Benefits: fewer casts, the compiler warns about unhandled cases in switch expressions, and the intent is clearer. Overuse on polymorphic types can be a smell — sometimes a virtual method is better.
decimal Fee(Account a) => a switch
{
{ IsVip: true } => 0m,
{ Balance: >= 10_000 } => 2m,
{ Balance: >= 0 and < 10_000 } => 5m,
_ => throw new ArgumentException("invalid")
};