How to use pattern matching in C#

By FoxLearn 1/3/2025 6:39:35 AM   62
Leverage the enhanced pattern matching capabilities in C# 8.0 to write code that is cleaner, more maintainable, and more efficient.

Originally introduced in C# 7.0, pattern matching allows you to work with any data type, including custom ones, to extract values from expressions. With the improvements in C# 8.0, this feature has been significantly expanded, offering a variety of new pattern types to further streamline your code.

Expressing patterns in C# 8.0

C# 8.0 introduces three types of patterns:

1. Positional patterns

Positional patterns leverage the Deconstruct method of a class and can include nested patterns, which is why they are also referred to as recursive patterns. In a recursive pattern, the result of one expression is used as input for another.

To use a positional pattern, you typically check for null values and then call the appropriate Deconstruct method to break down the properties of the object into separate variables. For example, consider the following Circle class:

public class Circle
{
    public int Radius { get; set; }
    public Circle(int radius) => Radius = radius;
    public void Deconstruct(out int radius) => radius = Radius;
}

You can apply the positional pattern to the Circle class like this:

Circle circle = new Circle(10);
var result = circle switch
{
    Circle(0) => "The radius is zero.",
    Circle(10) => "This is a circle with a radius of 10.",
    Circle(5) => "This is a circle with a radius of 5.",
    _ => "Default."
};
Console.WriteLine(result);

When you run this code, it will output:

This is a circle with a radius of 10.

2. Property patterns

Property patterns allow you to match an object based on its properties.

For instance, consider the following Product class:

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}

You can use property patterns to apply a discount based on the product's category.

public static decimal ApplyDiscount(Product product) =>
    product switch
    {
        { Category: "Electronics" } => product.Price * 0.90,  // 10% discount for Electronics
        { Category: "Clothing" } => product.Price * 0.85,     // 15% discount for Clothing
        { Category: "Groceries" } => product.Price * 0.95,     // 5% discount for Groceries
        _ => product.Price                                       // No discount for other categories
    };

You can call the method as follows:

static void Main(string[] args)
{
    Product product = new Product()
    {
        Name = "Smartphone",
        Price = 1000,
        Category = "Electronics"
    };

    decimal discountedPrice = ApplyDiscount(product);
    Console.WriteLine($"The discounted price is {discountedPrice:C}");
    Console.Read();
}

When you run this program, the console will display the following output:

The discounted price is $900.00

3. Tuple patterns

Tuple patterns in C# 8.0 allow you to evaluate multiple pieces of input simultaneously.

The following example demonstrates how tuple patterns can be used effectively:

private static string GetFavoriteGenres(string genre1, string genre2)
    => (genre1, genre2) switch
    {
        ("Rock", "Pop") => "Rock and Pop music.",
        ("Jazz", "Blues") => "Jazz and Blues music.",
        ("Classical", "Electronic") => "Classical and Electronic music.",
        (_, _) => "Unknown genre combination"
    };

Consider a tuple of music genres:

(string, string, string, string) musicGenres = ("Rock", "Pop", "Jazz", "Classical");
var genre1 = musicGenres.Item1.ToString();
var genre2 = musicGenres.Item2.ToString();
Console.WriteLine($"The music genres selected are: {GetFavoriteGenres(genre1, genre2)}");

When you run this program, it will output:

The music genres selected are: Rock and Pop music.

C# 8.0's tuple patterns allow you to efficiently check combinations of values in a concise and readable manner, improving the maintainability of your code.