DEV Community

Cover image for C# Pattern matching
Karen Payne
Karen Payne

Posted on

C# Pattern matching

Introduction

Learn about pattern matching with easy-to-follow code samples. The emphasis is on the code samples, as Microsoft documentation is good.

💡 Code was written using NET10, Microsoft Visual Studio 2026

Use the is expression, the switch statement, and the switch expression to match an input expression against any number of characteristics. C# supports multiple patterns, including declaration, type, constant, relational, property, list, var, and discard. You can combine patterns by using the Boolean logic keywords and, or, and not.

Above is Microsoft docs

Important

💡 The patterns presented should only be used once you understand a specific pattern, rather than forcing a pattern just because it's new.

Source code

Positional pattern

Use a positional pattern to deconstruct an expression and match the resulting values against the corresponding nested patterns.

Example

Here, rather than one value being evaluated in a switch, two values (properties on the class Money) are evaluated.


public sealed class Money(decimal amount, string currencyCode)
{
    public decimal Amount { get; } = amount;
    public string CurrencyCode { get; } = currencyCode;

    // Positional patterns use this method.
    public void Deconstruct(out decimal amount, out string currencyCode)
    {
        amount = Amount;
        currencyCode = CurrencyCode;
    }
}

public static void PositionalPatternSample()
{
    var list = MockData.MoneyList();

    foreach (var money in list)
    {
        Console.WriteLine($"{money.Amount,-5}{money.CurrencyCode}: {Identify(money)}");
    }
}

private static string Identify(Money money) => money switch
{
    (0m, "USD") => "No US dollars",
    ( > 0m and < 100m, "USD") => "Small US dollar amount",
    ( >= 100m, "USD") => "Large US dollar amount",
    ( > 0m, "EUR") => "Positive euro amount",
    ( < 0m, _) => "Negative amount",
    (_, _) => "Some other amount"
};
Enter fullscreen mode Exit fullscreen mode

Identify uses the Deconstruct of Money for the switch.

Data used

public static List<Money> MoneyList() =>
[
    new(0m, "USD"),
    new(25m, "USD"),
    new(125m, "USD"),
    new(50m, "EUR"),
    new(-10m, "USD")
];
Enter fullscreen mode Exit fullscreen mode

Results
console output

Property pattern

Use a property pattern to match an expression's properties or fields against nested patterns.

Example

Classes

public class Person : IPerson
{
    public int Id { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    public DateOnly BirthDate { get; set; }
    public int Age => BirthDate.GetAge();
    public Address? Address { get; set; }
}

public class Employee : IPerson
{
    public int Id { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    public DateOnly BirthDate { get; set; }
    public required string Badge { get; set; }
    public int Age => BirthDate.GetAge();
}

public class Address
{
    public string State { get; set; } = "";
    public string City { get; set; } = "";
}

Enter fullscreen mode Exit fullscreen mode

Data

public static List<IPerson> PeopleList() =>
[
    new Person { Id = 1, FirstName = "Alice", LastName = "Smith",
        BirthDate = new DateOnly(1990, 5, 15) ,
        Address = new Address { State = "OR", City = "Salem" }},

    new Person { Id = 2, FirstName = "Bob", LastName = "Johnson", 
        BirthDate = new DateOnly(1985, 11, 23) },
    new Employee() { Id = 3, FirstName = "Bob", LastName = "Johnson", 
        BirthDate = new DateOnly(1985, 11, 23), Badge = "AA1234"},
    new Person { Id = 4, FirstName = "Charlie", LastName = "Williams", 
        BirthDate = new DateOnly(2001, 1, 1)
    }
];
Enter fullscreen mode Exit fullscreen mode
public static void PropertyPatternSample()
{

    SpectreConsoleHelpers.PrintPink();

    if (MockData.PeopleList().FirstOrDefault() is not Person person) return;

    if (person is { Age: >= 18, Address.State: "OR" })
    {
        Console.WriteLine($"{person.FirstName} is an adult living in Oregon.");
    }

    string category = person switch
    {
        { Age: < 13 } => "Child",
        { Age: >= 13 and < 18 } => "Teenager",
        { Age: >= 18, Address.State: "OR" } => "Adult in Oregon",
        { Age: >= 18 } => "Adult",
        _ => "Unknown"
    };

    Console.WriteLine($"{category} - Age {person.Age}");

}
Enter fullscreen mode Exit fullscreen mode
  • Get the first item as a Person
  • The if statement asserts the person's age is greater than 18 and the address/state is Oregon.
  • The switch first two and last branch evalute age only while the third branch item asserts age and state property of address of the Person.

Results

console output

Declaration and type patterns

Use declaration and type patterns to check if the run-time type of an expression is compatible with a given type. By using a declaration pattern, you can also declare a new local variable. When a declaration pattern matches an expression, it assigns the variable to the converted expression result.

Example

Classes

public class Person : IPerson
{
    public int Id { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    public DateOnly BirthDate { get; set; }
    public int Age => BirthDate.GetAge();
    public Address? Address { get; set; }
}

public class Employee : IPerson
{
    public int Id { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    public DateOnly BirthDate { get; set; }
    public required string Badge { get; set; }
    public int Age => BirthDate.GetAge();
}

public class Address
{
    public string State { get; set; } = "";
    public string City { get; set; } = "";
}

Enter fullscreen mode Exit fullscreen mode

Data

public static List<IPerson> PeopleList() =>
[
    new Person { Id = 1, FirstName = "Alice", LastName = "Smith",
        BirthDate = new DateOnly(1990, 5, 15) ,
        Address = new Address { State = "OR", City = "Salem" }},

    new Person { Id = 2, FirstName = "Bob", LastName = "Johnson", 
        BirthDate = new DateOnly(1985, 11, 23) },
    new Employee() { Id = 3, FirstName = "Bob", LastName = "Johnson", 
        BirthDate = new DateOnly(1985, 11, 23), Badge = "AA1234"},
    new Person { Id = 4, FirstName = "Charlie", LastName = "Williams", 
        BirthDate = new DateOnly(2001, 1, 1)
    }
];
Enter fullscreen mode Exit fullscreen mode

The following code uses is pattern matching to distinguish between a Person and an Employee. In the if assertion below employee variable contains the Employee and its properties.

public static void DeclarationAndTypePatterns()
{

    SpectreConsoleHelpers.PrintPink();

    List<IPerson> list = MockData.PeopleList();

    foreach (var person in list)
    {
        AnsiConsole.MarkupLine($"[yellow]Person:[/][bold]{person.FirstName} {person.LastName}[/]");
        // Demonstrates the 'is' pattern matching
        if (person is Employee employee)
        {
            AnsiConsole.MarkupLine($"  [green]Is an Employee.[/]");
            AnsiConsole.MarkupLine($"  [green]Badge:[/]{employee.Badge}");
        }
        else
        {
            AnsiConsole.MarkupLine($"  [grey]Not an Employee.[/]");
        }

        // Demonstrates type pattern matching and property access
        AnsiConsole.MarkupLine($"  [cyan]Birth Date:[/]{person.BirthDate}");
    }
}
Enter fullscreen mode Exit fullscreen mode

Results

console output

Logical patterns

Use the not, and, and or pattern combinators to create in this case a check if an item is a Person or Employee.

public static void LogicalPattern()
{
    SpectreConsoleHelpers.PrintPink();

    List<IPerson> list = MockData.PeopleList();

    Console.WriteLine(list.FirstOrDefault() is not Employee ? "Not an employee" : "Is an employee");
}
Enter fullscreen mode Exit fullscreen mode

Disjunctive or pattern that matches an expression when either pattern matches the expression, as the following examples shows:

public static string GetSeasonSouthernHemisphere(DateTime date) => date.Month switch
{
    >= 9 and <= 11 => "Spring",
    >= 6 and <= 8 => "Summer",
    >= 3 and <= 5 => "Autumn",
    12 or 1 or 2 => "Winter",
    _ => "Not a valid month"
};

public static string GetSeasonNorthernHemisphere(DateTime date) => date.Month switch
{
    >= 3 and <= 5 => "Spring",
    >= 6 and <= 8 => "Summer",
    >= 9 and <= 11 => "Autumn",
    12 or 1 or 2 => "Winter",
    _ => "Not a valid month"
};
Enter fullscreen mode Exit fullscreen mode

Summary

Several useful patterns have been provided that can help write efficient code. When using these patterns in a team, ensure team members understand them.

Top comments (0)