C# Record Type: Nondestructive Mutation, Equality, DTOs, and More

DEFINING AND USING RECORDS IN C#

by

In this tutorial, you will learn about records in C#, a reference type introduced in C# 9 and further expanded in C# 10 with the introduction of record structs, a value type version of records. Records are specifically designed to encapsulate data and provide built-in functionality for immutability and value-based equality.

Defining Records

You can define records using the record keyword, and there are two distinct ways to declare properties for a record: positional parameters and standard property syntax.

Record Classes (Reference Types)

Here's an example of a record class with positional parameters:

public record Person(string FirstName, string LastName);

And the equivalent using standard property syntax:

public record Person
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
};

Record Structs (Value Types)

C# 10 introduced record structs, which are value types with similar functionality to record classes. Here's an example of a record struct with positional parameters:

public readonly record struct Point(double X, double Y, double Z);

And the equivalent using standard property syntax:

public record struct Point
{
    public double X { get; init; }
    public double Y { get; init; }
    public double Z { get; init; }
}

Mutable Records

Although records are primarily intended for immutable data models, you can create records with mutable properties and fields.

public record Person
{
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
};

Record structs can also be mutable, offering more flexibility:

public record struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}

Deconstruction

Records support deconstruction, allowing you to effortlessly separate their properties into variables.

var u = new User("John", "Doe", 980);
(string fname, string lname, int sal) = u;
Console.WriteLine($"{fname} {lname} earns {sal} per month");
record User(string FirstName, string LastName, int Salary);

Records as Data Transfer Objects (DTOs)

Records can be an excellent choice for Data Transfer Objects (DTOs) because they provide a concise way to define data structures with built-in immutability and value-based equality.

Here's an example of using a record as a DTO:

public record BookDto(int Id, string Title, string AuthorName);

public record BookDetailDto(int Id, string Title, int Year, decimal Price, string AuthorName, string Genre);

Compared to using classes for DTOs, records generate value-based equality checks (see below), init-only properties, and a JSON-like format for ToString() with less code. This makes records an efficient and convenient choice for defining DTOs in your applications.

Value-Based Equality

As previously mentioned, record types implement value-based equality by default, meaning that two instances of a record with the same values are considered equal.

The Bottom Line

In this tutorial, you learned about the fundamentals of records in C#, including defining record classes and record structs, creating mutable records, deconstruction, and value-based equality. You also learned a few practical scenarios where using record types can be helpful.


Don't stop learning!

There is so much to discover about C#. That's why I am making my favorite tips and tricks available for free. Enter your email address below to become a better .NET developer.


Did you know?

Our beautiful, multi-column C# reference guides contain more than 150 tips and examples to make it even easier to write better code.

Get your cheat sheets