C# 9.0 Records: Immutable Data Types Done Right

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.

Clean code
Photo by Clement Helardot on Unsplash

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.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.