C#  

Advanced C# Pattern Matching: Practical Examples Every Developer Should Know

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:

if (numbers is [.., 100])
{
    Console.WriteLine("Ends with 100.");
}

List patterns are useful for parsing structured input and validating sequences.

Nested Patterns

Pattern matching also supports nested object structures.

if (order is
{
    Customer:
    {
        IsActive: true
    },
    Total: > 1000
})
{
    Console.WriteLine("Priority order");
}

Complex object validation can often be expressed in a single readable statement.

Pattern Matching with Null Checks

Null checking becomes simpler using modern C# syntax.

Instead of:

if (customer != null)
{
    Process(customer);
}

Use:

if (customer is not null)
{
    Process(customer);
}

While functionally equivalent, the pattern matching form integrates naturally with other pattern expressions.

Best Practices

  • Use switch expressions for value mapping instead of lengthy switch statements.

  • Prefer property patterns over deeply nested if conditions.

  • Combine relational and logical patterns for readable range checks.

  • Use list patterns only when they improve clarity.

  • Keep pattern matching expressions focused and easy to understand.

  • Choose descriptive variable names in type patterns.

  • Refactor overly complex pattern expressions into smaller methods when necessary.

Common Mistakes

Overusing Nested Patterns

Pattern matching can express complex conditions, but deeply nested expressions quickly become difficult to read. If a pattern spans several lines and obscures business intent, consider extracting the logic into a well-named helper method.

Replacing Every if Statement

Not every conditional benefits from pattern matching. Simple conditions are often clearer with a straightforward if statement.

Ignoring Readability

Pattern matching should simplify code, not make it more cryptic. Favor readability over reducing the number of lines.

Forgetting Exhaustive Cases

When using switch expressions, always include a default (_) case or handle every possible input. This prevents unexpected runtime exceptions when new values are introduced.

Pattern Matching Features at a Glance

FeatureBest Use Case
Type patternsSafe type checking and casting
Property patternsObject validation
Relational patternsNumeric comparisons
Logical patternsCombining conditions
Switch expressionsMapping values to results
List patternsMatching array and collection structures
Nested patternsValidating complex object graphs

Conclusion

Pattern matching has become one of the most expressive features in modern C#, allowing developers to write cleaner, safer, and more maintainable code. Features such as type patterns, property patterns, relational patterns, logical operators, list patterns, and switch expressions reduce boilerplate while making business logic easier to understand.

However, as with any language feature, moderation is important. Pattern matching should simplify your code—not make it harder to follow. When used thoughtfully, it can replace verbose conditionals with expressive, declarative logic that improves both readability and maintainability.

By understanding the strengths of each pattern matching feature and applying them where they provide genuine clarity, you can take full advantage of modern C# and write code that is easier to maintain as your applications grow.