An in-depth Look at Advanced Pattern Matching in C# 12

Overview

C# 12 introduces exciting new features to its already robust pattern-matching capabilities. These enhancements provide developers with more expressive and concise ways to work with data, resulting in cleaner, more maintainable code. During this article, we'll discuss two major C# 12 enhancements: the let pattern and advanced recursive patterns. In order to make the most of these patterns, we'll explain how they work and their advantages and give you practical code examples.

What is Pattern Matching in C#?

The pattern matching feature in C# allows you to test values against specific patterns and extract data from them in a clear and concise manner. As introduced in C# 7 and expanded in subsequent versions, pattern matching has made it easier to work with complex data structures and switch expressions.

The pattern matching toolkit in C# 12 has been enhanced with:

  • The let pattern: Pattern matching with the let pattern introduces intermediate variables.
  • Recursive patterns: A recursive pattern allows matching and deconstructing more complex, nested objects.

Let's take a closer look at each of these features.

C# 12's let Pattern

A standout feature of C# 12 is the let pattern, which allows you to define and bind a variable inside a pattern match and reuse that value later on. When you want to reuse the result of an expression or when there are complex conditions involved, this is particularly useful.

Code Example of Basic let Pattern

namespace CSharp12.AdvancedPatternMatching;

public static class SimpleLetPattern
{
    public static string CheckDiscount(int age)
    {
        return age switch
        {
            // Using 'let' to introduce an intermediate variable
            
            let isAdult when isAdult >= 18 => "Eligible for discount",
            _ => "Not eligible for discount"
        };
    }
}

Let's introduce the isAdult variable, which contains the age value. The condition isAdult >= 18 evaluates within the pattern, returning "Eligible for a discount if age is 18 or over.".

Let Pattern Benefits

  1. Readability: It simplifies complex conditions into manageable chunks.
  2. Reusability: Multiple checks can reuse the computed value, reducing redundancy.
  3. Intermediate Calculations: Calculations within patterns can be reused in subsequent checks as intermediate calculations.

C# 12 Recursive Patterns

Positional and property patterns allow deep deconstruction and matching within nested data structures, such as complex objects or tuples.

Code Example of Recursive Pattern with Tuples

Here's a simple example of recursively matching a tuple:

namespace CSharp12.AdvancedPatternMatching;

public static class SimpleLetPattern
{
    public static string GetCoordinates((int X, int Y) point)
    {
        return point switch
        {
            // Recursive pattern matching on the tuple
            (0, 0) => "Origin",
            (let x, 0) when x > 0 => "X-axis positive",
            (0, let y) when y > 0 => "Y-axis positive",
            _ => "Somewhere else"
        };
    }
}

The elements of the tuple (int X, int Y) are recursively matched in this example.

  • A value of (0, 0) corresponds to the origin,
  • When x > 0 matches any point on the positive side of the X-axis (let x, 0),
  • (0, let y) corresponds to any positive Y-axis point when y > 0.

With the let pattern, you can easily assign parts of the tuple to variables and apply further conditions.

Code Example of Recursive Pattern in Objects

As an example, let's say you have nested classes or records, and you need to pattern match on deeply nested properties:

namespace CSharp12.AdvancedPatternMatching;

public class Address
{
    public string City { get; set; }=string.Empty;
    public string Country { get; set; } = string.Empty;
}
namespace CSharp12.AdvancedPatternMatching;
public class Person
{
    public string Name { get; set; }=string.Empty;
    public Address? Address { get; set; }

    public static string GetPersonLocation(Person person)
    {
        return person switch
        {
            // Recursive pattern matching on nested objects
            { Address: { City: "New York", Country: "USA" } } => "Person is in New York, USA",
            { Address: { Country: "USA" } } => "Person is in the USA",
            { Address: { City: var city } } => $"Person is in {city}",
            _ => "Location unknown"
        };
    }
}

In this case, we recursively match against the nested Address property of the Person object:

  • It returns a specific message if the person is in New York.
  • A general message is returned if the person is anywhere in the USA.
  • It prints the name of the city if only the city is known.

Advanced Recursive Pattern with Deconstruction

It is also possible to use deconstruction with recursive patterns, which is particularly useful when working with tuples and records.

namespace CSharp12.AdvancedPatternMatching;

public record Point(int X, int Y);
public static class Points
{
    public static string DescribePoint(Point point)
    {
        return point switch
        {
            // Matching recursively with deconstruction
            Point(0, 0) => "At the origin",
            Point(let x, let y) when x == y => "X equals Y",
            Point(let x, _) when x > 0 => "Positive X-coordinate",
            Point(_, let y) when y > 0 => "Positive Y-coordinate",
            _ => "Somewhere else"
        };
    }
}

The Point record can be deconstructed into its components X and Y as follows:

  • It returns "At the origin" if both are zero.
  • It returns "X equals Y" if X == Y.
  • Accordingly, it provides specific messages if X or Y are positive.

Using let and recursive patterns

In C# 12, you can combine the let pattern with recursive patterns to create more expressive and powerful code.

Code Example of  Complex Recursive Pattern with Let

namespace CSharp12.AdvancedPatternMatching;

public class TreeNode
{
    public int Value { get; set; }
    public TreeNode Left { get; set; }
    public TreeNode Right { get; set; }

    public static string SearchTree(TreeNode node)
    {
        return node switch
        {
            // Using let to capture the node value
            { Value: let value, Left: null, Right: null } => $"Leaf node with value {value}",
            { Left: { Value: let leftValue } } when leftValue > 0 => "Left child is positive",
            { Right: { Value: let rightValue } } when rightValue < 0 => "Right child is negative",
            _ => "Other node"
        };
    }
}

This example uses the let pattern to capture the values of the current node and its children. You can then apply conditions based on these values, which is particularly useful in tree-like data structures.

Summary

The advanced pattern matching features in C# 12 provide developers with powerful tools, including the let pattern and enhanced recursive patterns. Using these patterns, you can write concise, declarative logic that can handle complex conditions and deeply nested data with greater expressiveness and readability.

These new features will help you write cleaner, more maintainable code regardless of whether you're working with simple data structures or complex, recursive ones. The integration of advanced pattern-matching techniques into your projects will enhance your development experience as you begin using C# 12 and improve the quality of your applications as you use them.

Our C# code will become more expressive and easier to read and maintain when you use these advanced pattern-matching features.

The full code is available on my GitHub repository, and I would love for you to follow me on LinkedIn your support is greatly appreciated!


Recommended Free Ebook
Similar Articles
Capgemini
Capgemini is a global leader in consulting, technology services, and digital transformation.