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 and LastName are public, init-only properties. Their values can only be set during object initialization.
  • A Primary Constructor: A constructor that takes firstName and lastName as arguments.
  • Value-Based Equality: Two record instances are considered equal if all their property values are equal. The compiler generates Equals and GetHashCode implementations for you.
  • A Clean ToString(): The ToString() 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.