Published on
Reading Time
4 min read

Best Practice: Start using C# Records for DTOs instead of regular classes

Table of Contents

Introduction

C# 9 introduced records as a new language feature that provides an immutable data model. Records are perfect for modeling simple data transfer objects (DTOs) in your applications. In this post, I'll explain why you should start using records instead of regular classes for DTOs and show examples of how to use them effectively.

Why Use Records for DTOs?

DTOs are simple objects used to transfer data between different parts of an application. For example, you may have a CustomerDto class that is passed from a web API controller to a service layer.

Traditionally, these DTO classes are just regular classes like:

public class CustomerDto
{
  public int Id {get; set;}
  public string Name {get; set;}
}

However, records provide some key advantages for modeling DTOs:

  • Immutability - Records are immutable by default, meaning their property values cannot be changed after creation. This helps avoid accidental side effects in your code.

  • Value semantics - Records behave as values, not references. This makes them easier to reason about in your code.

  • With expressions - Records have baked-in support for with expressions to easily create updated copies.

  • Structural equality - Records provide built-in Equals and GetHashCode implementations based on their contents.

Overall, records are a better choice for simple DTO classes in most cases.

Using Records for DTOs

Let's look at an example of modeling a Customer DTO as a record:

public record CustomerDto
{
  public int Id {get; init;}
  public string Name {get; init;}
}

Note the get; init; on the properties - this makes them read-only as desired for an immutable DTO.

Also, records can offer a shorthand that automatically provides you with private read-only fields for your properties.  This shorthand allows you to go to this:

public record CustomerDto(int Id, string Name);

We can instantiate a CustomerDto just like a regular class:

var customer = new CustomerDto
{
  Id = 1,
  Name = "John Doe"
};

The key advantages of using a record here in this example are:

  • customer is immutable - we cannot change the Id or Name properties after creation
  • customer provides structural equality checks based on the property values
  • We could easily create an updated copy using with expressions:
var updated = customer with {Name = "Jane Doe"};

This creates a new CustomerDto instance with the Name changed.

Overall, records make it very easy to create simple, immutable DTO classes that avoid some common pitfalls.

When Not to Use Records

While records are great for DTOs in many cases, there are some situations where regular classes may still be preferable:

  • If you need mutability in your DTO, such as for a data binding scenario
  • If you need to define additional behavior or logic beyond just data storage
  • If you need inheritance in your object model (records do not support inheritance)

So consider the requirements before deciding between a record and regular class.

Putting into Practice

If you have not used records much yet, try adopting them for DTOs in your next project. Here are some tips:

  • Audit existing DTO classes - can any be converted to records?
  • Establish coding conventions using records for all new DTO types
  • Leverage records' value-based equality for improved testing
  • Use with expressions to minimize mutability bugs

Converting existing mutable DTOs may take some refactoring, but the long term benefits are worth it. Immutable DTOs using records will reduce bugs and improve the reasoning of your code over time.

Give C# 9 records a try for modeling simple DTO classes! They are a great new tool for .NET developers.