Pattern Matching in .NET

Sheldon Cohen
4 min readJul 8, 2024

--

Photo by Emma Smith on Unsplash

Pattern matching is a robust feature in the .NET ecosystem, enabling developers to write more expressive and concise code. The latest version of .NET introduces several enhancements to pattern matching, helping developers create clean and efficient code.

What is Pattern Matching?

At its core, pattern matching allows you to check a value against a pattern. It simplifies complex logical conditions, making your code easier to read and maintain. In .NET, pattern matching is utilized extensively with switch expressions, is expressions, and deconstruction.

For Beginners: Understanding Pattern Matching

For those new to pattern matching, think of it as a way to specify shapes (or patterns) that data can fit into. If the data fits the pattern, you can act on it accordingly. Let’s break this down with a simple analogy.

Imagine you have a box of assorted shapes: circles, squares, and triangles. Your task is to sort these shapes into different bins based on their type. Without pattern matching, you might have to pick up each shape, inspect it closely, and decide where it belongs based on its properties. This can be cumbersome and repetitive.

With pattern matching, you can define a rule that automatically recognizes and sorts the shapes. For instance:

  • If the shape is round and has no corners, it’s a circle.
  • If the shape has four equal sides, it’s a square.
  • If the shape has three sides, it’s a triangle.

In coding terms, this is like defining patterns that the shapes can fit into, and your program can then automatically handle each shape according to the rules you defined. This can help you write code that is easier to understand.

Extended Property Patterns

Property patterns have been extended to support nested patterns, allowing for more detailed and specific matches. This is particularly useful when working with complex objects.

Example:

Imagine you have a Person object with an Address property, and you want to check if the person lives in New York City.

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

public class Person
{
public string Name { get; set; }
public Address Address { get; set; }
}
var person = new Person { Name = "John", Address = new Address { City: "New York", State: "NY" } };
if (person is { Address: { City: "New York" } })
{
Console.WriteLine("Person lives in New York City.");
}

In this example, the pattern { Address: { City: "New York" } } checks if the Address property of the person object has a City property equal to “New York”.

Relational Patterns

Relational patterns enable comparisons using operators like <, <=, >, and >= directly within pattern matching.

Example:

Categorizing a person’s age:

int age = 25;

string category = age switch
{
< 18 => "Child",
>= 18 and < 65 => "Adult",
>= 65 and <= 120 => "Senior",
_ => "Amazing!!!"
};

Console.WriteLine(category); // Output: Adult

This example uses relational patterns to categorize ages into “Child,” “Adult,” and “Senior.”

Logical Patterns

Logical patterns combine patterns using logical operators and, or, and not.

Example:

Describing a number:

int number = 15;

string description = number switch
{
> 0 and < 10 => "Single digit positive number",
>= 10 and <= 20 => "Teen",
_ => "Other"
};

Console.WriteLine(description); // Output: Teen

Here, logical patterns and and or are used to match ranges of numbers.

List Patterns

List patterns enable matching against lists or arrays, allowing for more intuitive handling of collections.

Example:

Matching array patterns:

int[] numbers = { 1, 2, 3, 4, 5 };

string result = numbers switch
{
[1, 2, 3, ..] => "Starts with 1, 2, 3",
[.., 4, 5] => "Ends with 4, 5",
_ => "Other pattern"
};

Console.WriteLine(result); // Output: Starts with 1, 2, 3

In this example, the patterns [1, 2, 3, ..] and [.., 4, 5] match arrays starting with 1, 2, 3 and ending with 4, 5, respectively.

Practical Application and Usage

Simplifying Control Flow

Pattern matching can simplify complex control flow logic, making code easier to understand and maintain.

Example:

object obj = "Hello, World!"

if (obj is string s && s.Length > 5)
{
Console.WriteLine($"Long string: {s}");
}

This example checks if obj is a string and if its length is greater than 5.

Improved Type Safety

By using pattern matching, you can reduce the risk of runtime errors caused by invalid type casts.

Example:

object data = 123;

if (data is int number)
{
Console.WriteLine($"The number is {number}");
}

This example safely checks if data is an integer before using it.

Enhanced Readability

Pattern matching improves code readability by reducing the need for nested if-else statements and complex type checks.

Example:

object shape = new Circle { Radius = 5 };

string description = shape switch
{
Circle c when c.Radius > 0 => $"Circle with radius {c.Radius}",
Rectangle r when r.Width > 0 && r.Height > 0 => $"Rectangle with width {r.Width} and height {r.Height}",
_ => "Unknown shape"
};

Console.WriteLine(description);

In this example, the switch expression makes it easy to match different shapes and their properties.

Whether you are an experienced developer or new to the concept, using pattern matching can help you write cleaner, more maintainable code.

Read more about Pattern Matching on Microsoft’s Learning site.

Have comments, or thoughts? Drop a comment!

--

--

Sheldon Cohen

Technology professional with 15+ years of software development