How to Use Pattern Matching Switch Expressions in C
Pattern matching with switch expressions in C# provides a concise,
functional approach to conditional branching based on an object's type,
properties, or values. Introduced in C# 8.0 and expanded in subsequent
versions, switch expressions replace verbose switch
statements with lightweight expressions that return values. This article
covers the essential syntax and the most common pattern-matching
techniques—including type, relational, logical, property, and positional
patterns—to help you write cleaner and more expressive C# code.
Basic Syntax and the Discard Pattern
A switch expression evaluates an input expression and matches it
against a series of comma-separated arms. Each arm consists of a
pattern, a case guard (optional), the => operator, and
an expression to evaluate. The underscore (_) represents
the discard pattern, which acts as the default case.
int dayNumber = 3;
string dayName = dayNumber switch
{
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
4 => "Thursday",
5 => "Friday",
_ => "Weekend"
};Type Patterns and Declaration Patterns
Type patterns check whether a value can be cast to a specified type. A declaration pattern checks the type and simultaneously binds the cast value to a new variable for use in the arm.
public record Circle(double Radius);
public record Rectangle(double Width, double Height);
public double CalculateArea(object shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
null => throw new ArgumentNullException(nameof(shape)),
_ => throw new ArgumentException("Unknown shape", nameof(shape))
};Relational and Logical Patterns
Introduced in C# 9.0, relational patterns let you compare values
using <, <=, >, and
>=. Logical patterns use the keywords and,
or, and not to combine or invert patterns.
public string GetTemperatureDescription(double temperature) => temperature switch
{
< 0 => "Freezing",
>= 0 and <= 15 => "Cold",
> 15 and <= 25 => "Comfortable",
> 25 and < 35 => "Warm",
>= 35 => "Hot"
};Property Patterns
Property patterns match when an object's properties or fields match specified values or sub-patterns. You can evaluate multiple properties in a single pattern.
public record Order(decimal Subtotal, bool IsExpedited, string Country);
public decimal CalculateShipping(Order order) => order switch
{
{ Subtotal: >= 100m, Country: "US" } => 0m,
{ IsExpedited: true, Country: "US" } => 15m,
{ Country: "US" } => 5m,
{ Country: not "US" } => 25m,
_ => 10m
};Positional and Tuple Patterns
Positional patterns match values based on deconstruction. You can use
them directly on tuples or any type that defines a
Deconstruct method.
public string EvaluateCoordinates(int x, int y) => (x, y) switch
{
(0, 0) => "Origin",
(0, _) => "On Y-axis",
(_, 0) => "On X-axis",
( > 0, > 0) => "Quadrant 1",
( < 0, > 0) => "Quadrant 2",
( < 0, < 0) => "Quadrant 3",
( > 0, < 0) => "Quadrant 4"
};Case Guards with when
When a pattern alone cannot express the required condition, use the
when keyword to specify a boolean condition that must also
evaluate to true for the arm to match.
public decimal ApplyDiscount(decimal price, int customerLoyaltyYears) => (price, customerLoyaltyYears) switch
{
(var p, > 5) when p > 100m => p * 0.80m, // 20% discount
(var p, > 2) when p > 50m => p * 0.90m, // 10% discount
(var p, _) => p // No discount
};