- 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
andGetHashCode
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 creationcustomer
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.