Understanding Records and Pattern Matching in C# 9 and Above
Overview of Records and Pattern Matching
Records are a new reference type in C# that provide built-in functionality for encapsulating data. They are particularly useful for immutable data models, making them ideal for scenarios like data transfer objects or data structures in functional programming. Pattern Matching enhances the ability to check the shape of data and destructure complex types in a more readable way. Together, these features simplify code and improve maintainability.
Prerequisites
- Basic knowledge of C# and object-oriented programming concepts
- Familiarity with C# 8 or earlier versions
- Understanding of data structures and immutability
- Visual Studio or any C# compatible IDE installed
What are Records?
Records in C# are a special kind of class designed to hold data. They automatically provide implementations for Equals, GetHashCode, and ToString methods, making them ideal for data-centric applications.
public record Person(string FirstName, string LastName);
public class Program
{
public static void Main(string[] args)
{
var person1 = new Person("John", "Doe");
var person2 = new Person("John", "Doe");
Console.WriteLine(person1); // Outputs: Person { FirstName = John, LastName = Doe }
Console.WriteLine(person1 == person2); // Outputs: True
}
}In this code example:
- We define a record named Person with two properties: FirstName and LastName.
- In the Main method, we create two instances of Person, person1 and person2, with the same values.
- When we print person1, it uses the autogenerated ToString method to display its properties.
- We compare person1 and person2 using the equality operator, which returns true because records implement value-based equality.
Immutable Data with Records
One of the most significant advantages of records is that they are immutable by default. This means that once you create an instance of a record, its state cannot be changed. This immutability leads to safer and more predictable code.
public record Address(string Street, string City);
public class Program
{
public static void Main(string[] args)
{
var address = new Address("123 Main St", "New York");
// address.Street = "456 Elm St"; // This line would cause a compile-time error
var newAddress = address with { Street = "456 Elm St" };
Console.WriteLine(newAddress); // Outputs: Address { Street = 456 Elm St, City = New York }
}
}In this example:
- We define a record named Address with two properties: Street and City.
- We create an instance of Address named address.
- A comment indicates that trying to change address.Street directly would result in a compile-time error due to immutability.
- We use the with expression to create a new instance newAddress based on address, changing only the Street property.
Pattern Matching Basics
Pattern matching allows you to check the shape of objects and destructure them in a clear and concise way. It is especially useful in scenarios involving conditional logic.
public class Shape
{
public record Circle(double Radius);
public record Square(double Side);
public static void PrintShapeInfo(object shape)
{
switch (shape)
{
case Circle circle:
Console.WriteLine($"Circle with radius: {circle.Radius}");
break;
case Square square:
Console.WriteLine($"Square with side length: {square.Side}");
break;
default:
Console.WriteLine("Unknown shape");
break;
}
}
public static void Main(string[] args)
{
var circle = new Circle(5.0);
var square = new Square(4.0);
PrintShapeInfo(circle); // Outputs: Circle with radius: 5
PrintShapeInfo(square); // Outputs: Square with side length: 4
}
}In this code:
- We define a class Shape that contains two records: Circle and Square.
- The PrintShapeInfo method uses a switch statement to perform pattern matching on the shape object.
- For each case, we can access the properties of the matched shape type directly, allowing for clean and readable code.
Advanced Pattern Matching Features
C# 9 introduced advanced pattern matching features, including relational patterns and logical patterns, which allow for more complex conditional checks.
public class Temperature
{
public double Value { get; set; }
public string Scale { get; set; }
}
public class Program
{
public static void DescribeTemperature(Temperature temp)
{
switch (temp)
{
case { Value: > 30, Scale: "Celsius" }:
Console.WriteLine("It's a hot day!");
break;
case { Value: < 0, Scale: "Celsius" }:
Console.WriteLine("It's freezing!");
break;
case { Value: var value, Scale: "Fahrenheit" } when value > 86:
Console.WriteLine("It's a hot day in Fahrenheit!");
break;
default:
Console.WriteLine("Temperature is normal.");
break;
}
}
public static void Main(string[] args)
{
var temp1 = new Temperature { Value = 35, Scale = "Celsius" };
var temp2 = new Temperature { Value = -5, Scale = "Celsius" };
var temp3 = new Temperature { Value = 90, Scale = "Fahrenheit" };
DescribeTemperature(temp1); // Outputs: It's a hot day!
DescribeTemperature(temp2); // Outputs: It's freezing!
DescribeTemperature(temp3); // Outputs: It's a hot day in Fahrenheit!
}
}In this code:
- We create a class Temperature with properties Value and Scale.
- The DescribeTemperature method uses pattern matching to check the temperature's value and scale.
- We use relational patterns (e.g., Value: > 30) and logical patterns (e.g., when) to create sophisticated conditions.
Best Practices and Common Mistakes
Best Practices:
- Use records for data structures that require value equality.
- Make use of the with expression for immutability and clarity.
- Leverage pattern matching to make your code more expressive and easier to understand.
Common Mistakes:
- Forgetting that records are immutable by default, which can lead to confusion when trying to modify properties.
- Using pattern matching without considering all possible cases, which can lead to unhandled scenarios.
- Overusing pattern matching for simple checks where traditional conditional statements would suffice.
Conclusion
Records and pattern matching in C# 9 and above provide powerful tools for building clean, maintainable, and expressive code. By understanding how to effectively use records for data encapsulation and pattern matching for conditional logic, developers can enhance their C# programming skills significantly.