Unleashing the Power of C# 9: A Deep Dive into Records and Pattern Matching

Introduction: C# 9, released as part of .NET 5, brought an array of exciting features that significantly improve the developer experience. In this article, we'll take a deep dive into two of the most powerful features - records and pattern-matching enhancements, and understand how they can be used to write cleaner and more maintainable code.

Section 1: Understanding Records:

1.1 What are Records? Records are a new kind of reference type in C# 9 and are best suited for immutable data models. They provide a concise syntax for defining classes that primarily act as containers for data.

1.2 Creating a Record

public record Person(string FirstName, string LastName);

This creates a record with two properties.

1.3 Benefits of Using Records:

  • Immutability: Records are immutable by default.

  • Value-based Equality: Records use value-based equality.

  • Concise Syntax: Less code clutter.

1.4 With Expressions Records support with expressions, allowing you to create a new record that is a copy of an existing one, with some values changed.

var jane = new Person("Jane", "Doe");
var janeSmith = jane with { LastName = "Smith" };

1.5 Positional and Nominal Records Positional records use a concise syntax to define properties, while nominal records allow you to manually define properties.

public record PositionalPerson(string FirstName, string LastName); // Positional
public record NominalPerson { public string FirstName { get; init; } public string LastName { get; init; } }; // Nominal

1.6 Deconstructing Records Records can be deconstructed into variables.

var john = new Person("John", "Doe");
var (firstName, lastName) = john;
Console.WriteLine(firstName); // Output: John

Section 2: Pattern Matching Enhancements:

2.1 Relational Patterns: You can compare values using relational operators.

public static string Describe(int number) => number switch
{
    < 0 => "Negative number",
    0 => "Zero",
    > 0 => "Positive number"
};

2.2 Logical Patterns: Combine patterns with logical operators and, or, and not.

public static string GetSize(int number) => number switch
{
    < 0 => "Negative number",
    0 => "Zero",
    > 1 and <= 10 => "Small number",
    > 10 => "Large number"
};

2.3 Property Patterns: Pattern match based on object properties.

public static string DescribeShape(Shape shape) => shape switch
{
    { Width: 0, Height: 0 } => "Point",
    { Width: > 0, Height: > 0 } => "Rectangle",
    null => "Null shape",
    _ => "Unknown shape"
};

Section 3: Combining Records with Pattern Matching: Combining records and pattern matching leads to very expressive code.

Example:

public record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;

public static double CalculateArea(Shape shape) => shape switch
{
    Circle c => Math.PI * c.Radius * c.Radius,
    Rectangle r => r.Width * r.Height,
    _ => throw new ArgumentException("Unknown shape")
};

Section 4: Practical Applications:

4.1 Using Records for DTOs (Data Transfer Objects): Records are perfect for creating Data Transfer Objects, as they can be made immutable and only hold data.

4.2 Leveraging Pattern Matching for Complex Decision Making: Enhanced pattern matching can be used for building decision-making logic such as those required in rule engines.

4.3 Domain-Driven Design with Records: Records can be used effectively in Domain-Driven Design for value objects.

Conclusion: C# 9 with its introduction of records and enhancements in pattern matching brings forth powerful tools for developers. Leveraging these features can lead to cleaner, more expressive, and more maintainable code. Embracing these modern features is essential to keep pace with the evolving landscape of software development.