Pattern matching has evolved significantly since its introduction in C# 7. What began as a convenient way to check object types has grown into a powerful language feature that simplifies conditional logic, improves readability, and reduces boilerplate code.
Modern C# versions introduce property patterns, relational patterns, logical patterns, list patterns, and enhanced switch expressions, enabling developers to express complex conditions in a concise and maintainable way.
In this article, we'll explore advanced pattern matching techniques with practical examples and discuss when they improve code quality—and when they may reduce readability.
Why Use Pattern Matching?
Traditional conditional logic often involves multiple if statements, type checks, and explicit casts.
For example:
if (shape is Circle)
{
var circle = (Circle)shape;
Console.WriteLine(circle.Radius);
}
Pattern matching combines the type check and variable declaration into a single expression.
if (shape is Circle circle)
{
Console.WriteLine(circle.Radius);
}
The result is cleaner, safer, and easier to read.
Type Patterns
Type patterns allow you to verify an object's type while creating a strongly typed variable.
public static double CalculateArea(object shape)
{
return shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
Triangle t => t.Base * t.Height / 2,
_ => throw new ArgumentException("Unknown shape")
};
}
This eliminates explicit casts and keeps the implementation concise.
Property Patterns
Property patterns evaluate object properties directly without nested conditionals.
Instead of:
if (customer != null &&
customer.IsActive &&
customer.Orders > 10)
{
Console.WriteLine("Premium customer");
}
Use:
if (customer is
{
IsActive: true,
Orders: > 10
})
{
Console.WriteLine("Premium customer");
}
Property patterns are particularly useful when validating DTOs, API requests, or domain objects.
Relational Patterns
Relational patterns simplify numeric comparisons.
string category = price switch
{
< 100 => "Budget",
>= 100 and < 500 => "Standard",
>= 500 and < 1000 => "Premium",
_ => "Luxury"
};
Compared to multiple if statements, the intent is much clearer.
Logical Patterns
Logical patterns combine conditions using and, or, and not.
Example:
if (age is >= 18 and < 65)
{
Console.WriteLine("Working age");
}
Using or:
if (status is "Pending" or "Processing")
{
Console.WriteLine("Order is active.");
}
Using not:
if (customer is not null)
{
Console.WriteLine(customer.Name);
}
These operators improve readability by replacing deeply nested conditions.
Switch Expressions
Modern switch expressions are concise and expressive.
Traditional switch:
switch (day)
{
case DayOfWeek.Saturday:
case DayOfWeek.Sunday:
return false;
default:
return true;
}
Modern expression:
return day switch
{
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false,
_ => true
};
Switch expressions work particularly well for mapping values and implementing business rules.
List Patterns
List patterns, introduced in newer versions of C#, allow matching arrays and collections by structure.
Example:
if (numbers is [1, 2, 3])
{
Console.WriteLine("Exact sequence.");
}
Matching a prefix:
if (numbers is [0, ..])
{
Console.WriteLine("Starts with zero.");
}
Matching a suffix:
Join the conversation! Your thoughts help the community grow.