Pattern matching
Test the shape of data — its type, properties and contents — in one readable expression.
12 min read
You will learn
- Use type patterns with is and switch
- Match on object properties with property patterns
- Combine patterns with and, or and not
- Match arrays and lists with list patterns
Asking "what does this data look like?"
Code constantly inspects data: is this payment a card or a bank transfer? Is the order heavy and international? Is the list empty? Writing those checks with nested ifs and casts is clumsy. Pattern matching lets you describe the shape you're looking for directly.
You've already used simple patterns in switch expressions (>= 90, "Saturday" or "Sunday", _). Let's see the full toolbox.
Type patterns
Test a type and get a typed variable in one step:
object payment = GetPayment();
if (payment is CardPayment card)
{
Console.WriteLine($"Card ending in {card.Last4}"); // card is a CardPayment here
}Before patterns, this needed is followed by a cast. Combine type patterns in a switch:
decimal Fee(Payment p) => p switch
{
CardPayment c => c.Amount * 0.029m,
BankTransfer => 0.50m, // type check only, no variable needed
_ => 0,
};Constant, relational and logical patterns
string Category(int age) => age switch
{
< 0 => throw new ArgumentOutOfRangeException(nameof(age)),
0 => "newborn",
< 13 => "child",
>= 13 and < 20 => "teenager",
_ => "adult",
};
if (input is not null) { ... }
if (c is >= 'a' and <= 'z' or >= 'A' and <= 'Z') { ... } // a letternot null is the idiomatic null check.
when guards
Add an arbitrary condition to an arm:
string Describe(object o) => o switch
{
int n when n % 2 == 0 => "even number",
int => "odd number",
string s when s.Length > 10 => "long text",
string => "short text",
_ => "something else",
};Property patterns
Match on an object's properties — and nest patterns inside:
record Order(string Country, decimal WeightKg, bool Express, Customer Customer);
decimal Shipping(Order o) => o switch
{
{ Express: true } => 25,
{ Country: "FR", WeightKg: < 1 } => 5,
{ Country: "FR" } => 10,
{ Customer: { IsVip: true } } => 0, // nested
{ Customer.IsVip: true } => 0, // same thing, shorter
_ => 20,
};Arms are checked top to bottom, so order them from most specific to most general.
Positional patterns
Types with a Deconstruct method — records, tuples — can be matched by position:
string Quadrant(Point p) => p switch
{
(0, 0) => "origin",
( > 0, > 0) => "top right",
( < 0, > 0) => "top left",
_ => "bottom half or an axis",
};
// Tuples make multi-value decisions tidy:
string Winner(string a, string b) => (a, b) switch
{
("rock", "scissors") or ("scissors", "paper") or ("paper", "rock") => "Player A",
var (x, y) when x == y => "Draw",
_ => "Player B",
};List patterns
Match arrays and lists by their elements:
string Describe(int[] values) => values switch
{
[] => "empty",
[var only] => $"just {only}",
[1, ..] => "starts with 1",
[.., 0] => "ends with 0",
[var first, .., var last] => $"{first} … {last}",
};
if (args is ["--help" or "-h"]) ShowHelp();.. matches any number of elements (including none).
Tip
The compiler checks switch expressions for exhaustiveness: if some inputs aren't covered, you get a warning, and an unmatched value throws SwitchExpressionException at run time. A final _ arm covers everything.
Try it
C# runtime not loaded
Key takeaways
is Type nametests a type and introduces a variable;is not nullchecks for null.- Relational (
< 5), logical (and,or,not) and constant patterns combine freely. - Property patterns
{ Prop: pattern }and positional patterns(a, b)match object contents. - List patterns
[first, .., last]match sequences;whenadds custom conditions.
Exercise
Describe classifies any object (see the tests for the exact texts). ShippingCost applies the rules: express orders cost 25; orders to "FR" cost 5 under 1 kg and 10 otherwise; any other order over 10 kg costs 50, the rest 20. Classify describes an array: "empty", "one: X" or "first..last".
C# runtime not loaded
Hint 1
Describe: value switch { null => "null", int n when n < 0 => ..., 0 => "zero", int => ..., "" => ..., string s => ..., _ => "something else" }.
Hint 2
ShippingCost: property patterns like { Express: true } => 25 and { Country: "FR", WeightKg: < 1 } => 5. Order the arms from most to least specific.
Hint 3
Classify: list patterns — [] => "empty", [var only] => ..., [var first, .., var last] => ...
Show solution
Console.WriteLine(Describe(42));
Console.WriteLine(ShippingCost(new Order("FR", 0.5m, false)));
Console.WriteLine(Classify(new[] { 1, 2, 3 }));
string Describe(object? value) => value switch
{
null => "null",
int n when n < 0 => "negative int",
0 => "zero",
int => "positive int",
"" => "empty string",
string s => $"string of length {s.Length}",
_ => "something else",
};
decimal ShippingCost(Order order) => order switch
{
{ Express: true } => 25,
{ Country: "FR", WeightKg: < 1 } => 5,
{ Country: "FR" } => 10,
{ WeightKg: > 10 } => 50,
_ => 20,
};
string Classify(int[] values) => values switch
{
[] => "empty",
[var only] => $"one: {only}",
[var first, .., var last] => $"{first}..{last}",
};
record Order(string Country, decimal WeightKg, bool Express);Quiz
4 questions · 3 correct to pass1.What does `if (shape is Circle c)` do?
2.What does the pattern `{ Country: "FR", WeightKg: < 1 }` match?
3.Which value matches `is >= 10 and <= 20`?
4.What does the list pattern `[1, .., 9]` match?
To complete this lesson
- ○ Solve the exercise
- ○ Pass the quiz (3/4 correct)