With C# 9.0, Microsoft introduced the record keyword, a powerful new addition that makes it easier to work with immutable data. Records are designed to simplify the creation of classes that are primarily intended to store data, and they offer a more concise and expressive way to model data compared to traditional classes. If you’ve ever found yourself writing classes that are mostly getters, setters, and boilerplate code for equality comparisons, records are designed with you in mind.
What are Records?
Records are reference types, like classes, but they are intended to be used as data carriers. They have built-in functionality for value equality, immutability, and concise syntax, making them ideal for creating data-centric applications where immutability and value-based equality are desired.
In essence, a record is a type that provides:
- Immutable Properties: By default, records are immutable. Once created, their properties can’t be changed, ensuring that the data remains consistent.
- Value Equality: Unlike classes, which are compared by reference, records are compared by the values of their properties. This makes them perfect for scenarios where two instances with the same data should be considered equal.
Declaring Records
Declaring a record is straightforward and concise. Here’s a basic example of how to define a Player record:
public record Player(int Id, string Name);
This one line of code automatically generates a class with:
- Immutable properties (
IdandName). - A constructor that takes values for these properties.
- Deconstruct methods.
- Value-based equality methods (
Equals,GetHashCode). ToStringmethod, which outputs a readable string representation of the record.
Using Records
Records are useful when you want to return a data object from a method, ensuring that the object’s state won’t change. Here’s a practical example using a Player record:
public record Player(int Id, string Name);
public class Game
{
public Player GetPlayer()
{
return new Player(1, "PlayerOne");
}
}
var game = new Game();
var player = game.GetPlayer();
Console.WriteLine(player); // Output: Player { Id = 1, Name = PlayerOne }
Immutability in Records
By default, the properties in a record are immutable. However, you can use with expressions to create a copy of a record with some properties modified:
var player = new Player(1, "PlayerOne");
var updatedPlayer = player with { Name = "PlayerTwo" };
Console.WriteLine(player); // Output: Player { Id = 1, Name = PlayerOne }
Console.WriteLine(updatedPlayer); // Output: Player { Id = 1, Name = PlayerTwo }
In the example above, the with expression creates a new Player instance, copying the original values and modifying only the specified properties. This is especially useful when you need to alter a record’s state without affecting the original instance, maintaining immutability while enabling easy updates.
Value Equality
One of the standout features of records is value equality. When you compare two records, they are considered equal if all their properties are equal, unlike classes which use reference equality by default.
var player1 = new Player(1, "PlayerOne");
var player2 = new Player(1, "PlayerOne");
Console.WriteLine(player1 == player2); // Output: True, because the values are equal.
This is particularly useful in scenarios like unit testing, where value-based comparisons are often required.
Benefits of Using Records
- Concise Syntax: Records reduce boilerplate code, making your data models more readable and maintainable.
- Immutability: Built-in immutability ensures that your data remains consistent and reduces side effects.
- Value-Based Equality: Records automatically implement value equality, making comparisons straightforward.
- Pattern Matching: Records work seamlessly with C#’s pattern matching features, enhancing the readability and expressiveness of your code.
When to Use Records
- Data-Centric Applications: Use records when your application heavily relies on data modeling, and you want concise, immutable types.
- Value Comparisons: Use records when you need to compare objects by their values rather than references.
- Functional Programming: Records align well with functional programming paradigms, where immutability and data consistency are key.
When to Avoid Records
- Mutable State Requirements: If your application requires mutable state or object identity, traditional classes or structs may be more appropriate.
- Performance Sensitivity: Records are reference types, so for high-performance or low-memory scenarios, consider structs instead.
Conclusion
The record keyword in C# 9.0 offers a powerful way to work with data-focused objects. With their immutable properties, value-based equality, and concise syntax, records help streamline code and enhance maintainability. By using records appropriately, you can write cleaner, more reliable, and more expressive .NET applications. Whether you’re building data-centric applications, implementing functional programming paradigms, or simply reducing boilerplate, records are a feature worth trying.