A Guide to C# Records
An introduction to C# 9 records, a new reference type that provides a simplified syntax for creating immutable data objects. Learn how records can help you reduce boilerplate and write more robust, data-centric code.
One of the headline features of C# 9 is the introduction of a new kind of type declaration: the record. Records are designed to solve a very common problem: creating simple, immutable data-holding objects. They provide a concise syntax for what would otherwise require a lot of boilerplate code.
The Problem: Boilerplate in Data Classes
Before records, if you wanted to create a simple, immutable class to represent a person, you would have to write a lot of code:
public class Person
{
public string FirstName { get; }
public string LastName { get; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
// And you'd also need to override Equals, GetHashCode, and ToString
// for correct value-based equality and nice printing...
}
This is a lot of ceremony for a simple data container.
The Solution: Records
Records automate all of this. With a single line of code, you can declare a record with the same functionality.
public record Person(string FirstName, string LastName);
This one line creates a Person
type with the following features out of the box:
- Immutable Properties:
FirstName
andLastName
are public,init-only
properties. Their values can only be set during object initialization. - A Primary Constructor: A constructor that takes
firstName
andlastName
as arguments. - Value-Based Equality: Two record instances are considered equal if all their property values are equal. The compiler generates
Equals
andGetHashCode
implementations for you. - A Clean
ToString()
: TheToString()
method is overridden to provide a clean, readable representation of the object (e.g.,Person { FirstName = Alice, LastName = Smith }
). - Deconstruction: A
Deconstruct
method is generated, allowing you to easily extract the property values into variables.
var person1 = new Person("Alice", "Smith");
var person2 = new Person("Alice", "Smith");
Console.WriteLine(person1); // Prints a nice string representation
Console.WriteLine(person1 == person2); // True! (Value-based equality)
var (fn, ln) = person1; // Deconstruction
Non-Destructive Mutation with the with
Expression
Because records are designed to be immutable, you don't change their state after they are created. Instead, you create a new instance with the modified values. Records make this easy with the with
expression.
The with
expression creates a copy of an existing record instance, allowing you to specify new values for certain properties.
var person1 = new Person("Alice", "Smith");
// Create a new person record with a different last name
var person2 = person1 with { LastName = "Jones" };
Console.WriteLine(person1.LastName); // "Smith"
Console.WriteLine(person2.LastName); // "Jones"
This is a powerful feature for working with immutable data in a concise way.
When to Use Records
Records are perfect for any situation where you need a simple, immutable container for data, such as:
- Data Transfer Objects (DTOs) for carrying data between layers of your application or over the network.
- Value Objects in Domain-Driven Design.
- Representing events or messages in an event-driven system.
- Any time you find yourself writing a class where the primary purpose is just to hold data.
Records vs. Classes
It's important to remember that records are still reference types (like classes), not value types (like structs). The key difference is their intended purpose and the compiler-generated features.
- Use a class when you need to define an object with a distinct identity and state that can change over time.
- Use a record when you want to define a type whose primary purpose is to represent a value, and that value is defined by its constituent properties.
Conclusion
Records are a fantastic addition to the C# language. They provide a simple, concise, and powerful way to create immutable data types, reducing boilerplate and making your code more readable and robust. For data-centric programming in C#, records should be your new default choice.