Records are C# 9’s most impactful feature. They provide a concise syntax for creating immutable reference types with value-based equality. If you’ve ever written a DTO class with equals, hashcode, and toString – records are about to save you hours of boilerplate.
The Problem Records Solve
Before records, creating a proper immutable data class required a lot of code:
// Before: ~50 lines per class
public class Person
{
public string FirstName { get; }
public string LastName { get; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public override bool Equals(object obj) => // 10 more lines
public override int GetHashCode() => // 5 more lines
public override string ToString() => // ...
public void Deconstruct(out string first, out string last) => // ...
}
// After: 1 line!
public record Person(string FirstName, string LastName);
Value Equality
Unlike classes, two record instances are equal if their values are equal:
var person1 = new Person("John", "Doe");
var person2 = new Person("John", "Doe");
// With classes: false (reference equality)
// With records: true (value equality)
Console.WriteLine(person1 == person2); // True
// Also works with collections
var set = new HashSet<Person> { person1 };
Console.WriteLine(set.Contains(person2)); // True
With-Expressions for Immutable Updates
Records support “with expressions” for non-destructive mutation – creating a new instance with some properties changed:
var john = new Person("John", "Doe");
// Create a new record with LastName changed
var jane = john with { FirstName = "Jane" };
// john is unchanged (immutable)
// jane is a new instance with FirstName = "Jane", LastName = "Doe"
Console.WriteLine(john); // Person { FirstName = John, LastName = Doe }
Console.WriteLine(jane); // Person { FirstName = Jane, LastName = Doe }
Positional Records
The compact syntax with parameters creates a “positional record” with auto-generated deconstruction:
public record Person(string FirstName, string LastName);
var person = new Person("John", "Doe");
// Deconstruction works automatically
var (first, last) = person;
Console.WriteLine(first); // John
// Pattern matching friendly
if (person is Person("John", _))
{
Console.WriteLine("Found John!");
}
When to Use Records
- DTOs and API responses – Immutable data transfer
- Domain events – Events are facts that happened, should never change
- Value objects – Domain-driven design value objects
- Configuration – Immutable settings objects
- Functional programming – Immutability as default
Key Takeaways
- Records provide concise syntax for immutable reference types
- Value-based equality means instances with same data are equal
- With-expressions create modified copies without mutation
- Perfect for DTOs, events, and functional programming patterns
References
Discover more from C4: Container, Code, Cloud & Context
Subscribe to get the latest posts sent to your email.