MSDN Community

.NET Development & Innovation

Exploring Advanced C# Features: Patterns Matching & Records

Welcome back to our .NET Community blog! In this post, we're diving deep into some of the most powerful and modern features introduced in recent C# versions: Pattern Matching and Records. These features significantly enhance code readability, reduce boilerplate, and enable more expressive and robust programming.

Pattern Matching in C#

Pattern matching allows you to check a value against a series of patterns and execute code based on which pattern it matches. It extends the capabilities of `is` and `switch` statements, making them more versatile.

Type Patterns

The most basic form, checking if an object is of a certain type and optionally assigning it to a variable.


if (obj is int i)
{
    Console.WriteLine($"It's an integer with value: {i}");
}
else if (obj is string s)
{
    Console.WriteLine($"It's a string with length: {s.Length}");
}
                    

Relational Patterns

Compare properties or values against constants.


int score = 85;
string grade = score switch
{
    > 90 => "A",
    > 80 => "B",
    > 70 => "C",
    _ => "D"
};
Console.WriteLine($"Your grade is: {grade}");
                    

Logical Patterns

Combine patterns using logical operators like `and`, `or`, and `not`.


if (x is >= 0 and < 100)
{
    Console.WriteLine("Value is within the range [0, 99].");
}
                    

Property Patterns

Deconstruct objects based on their properties.


public record Point(int X, int Y);

Point p = new Point(10, 20);

if (p is { X: 10, Y: 20 })
{
    Console.WriteLine("The point is at (10, 20).");
}
                    

Recursive Patterns

Pattern matching on nested properties or elements of collections.


public record Address(string Street, string City, string ZipCode);
public record Person(string Name, Address Location);

Person person = new Person("Alice", new Address("Main St", "Anytown", "12345"));

if (person is { Location: { City: "Anytown" } })
{
    Console.WriteLine($"{person.Name} lives in Anytown.");
}
                    

Records in C#

Records are a special kind of class or struct that are primarily used for holding immutable data. They automatically provide value-based equality, immutability, and simplified syntax for creating data-centric types.

Declaring a Record

Using the record keyword simplifies the declaration significantly.


public record Customer(int Id, string Name, string Email);
                    

This single line generates a class with:

  • Public read-only properties: Id, Name, Email.
  • A constructor that initializes these properties.
  • An overridden Equals and GetHashCode for value-based equality.
  • An overridden ToString for a readable representation.
  • Methods for Deconstruct.

Immutability and Value-Based Equality

By default, record properties are immutable. Two record instances are considered equal if all their properties have the same values, not just if they refer to the same object in memory.


var customer1 = new Customer(1, "Bob Smith", "bob@example.com");
var customer2 = new Customer(1, "Bob Smith", "bob@example.com");
var customer3 = new Customer(2, "Alice Wonderland", "alice@example.com");

Console.WriteLine(customer1 == customer2); // Output: True
Console.WriteLine(customer1.Equals(customer2)); // Output: True
Console.WriteLine(customer1 == customer3); // Output: False
                    

Record With Expressions (Immutability++)

Records provide a convenient way to create new instances based on existing ones with modified properties, all while maintaining immutability.


var updatedCustomer = customer1 with { Email = "b.smith@example.com" };
Console.WriteLine(updatedCustomer);
// Output: Customer { Id = 1, Name = "Bob Smith", Email = "b.smith@example.com" }
Console.WriteLine(customer1); // Original remains unchanged
// Output: Customer { Id = 1, Name = "Bob Smith", Email = "bob@example.com" }
                    

Inheritance with Records

Records can inherit from other records, allowing for data hierarchy.


public record Person(string Name, int Age);
public record Employee(int EmployeeId, string Name, int Age) : Person(Name, Age);

var emp = new Employee(101, "Charlie Brown", 30);
Console.WriteLine(emp.Name); // Output: Charlie Brown
                    

Why Use These Features?

Pattern Matching makes conditional logic more readable and less error-prone, especially when dealing with complex types or states. It reduces the need for verbose type checks and casting.

Records are ideal for creating simple, immutable data containers. They cut down on boilerplate code significantly, improve type safety, and make working with data structures a breeze, particularly in scenarios like DTOs, state management, or functional programming paradigms.

Mastering these C# features will undoubtedly elevate your development skills, leading to cleaner, more maintainable, and more robust applications. Happy coding!

Further Reading: