A C# 13 Tip for building Data Transfer Objects (DTOs) Using Records

Overview

C# 9 introduced a new data type called records that is ideal for defining immutable data models. Records provide value semantics, while classes focus on reference semantics. For example, when creating Data Transfer Objects (DTOs), data integrity, concise syntax, and automatic method generation are essential, records are ideal.

We will explore why records are ideal for DTOs and provide practical examples using C# 13.

How do Data Transfer Objects (DTOs) work?

Data Transfer Objects are simple objects created to transport data between different parts of an application. They encapsulate data and hide internal data structures or implementation details, allowing loose coupling between layers of an application.

Benefits of DTOs

  • Encapsulation: Complex data structures can be hidden through encapsulation.
  • Separation of Concerns: Identify internal models and external data and separate them.
  • Performance Optimization: Minimize data transfer payloads for performance optimization.

What are the benefits of using records for DTOs?

DTOs are an excellent fit for C# records since they.

Readability and Conciseness

Data models are easier to understand and maintain when records have a clean and straightforward syntax.

Code Example

namespace CSharp13RecordsDTOs.Dtos;
public record CustomerDto(string FirstName, string LastName, string Email);
try
{
    var customerExampleOne = new CustomerDto("Ziggy", "Rafiq", "[email protected]");
    Console.WriteLine(customerExampleOne);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

Immutability (by Default)

By default, records are immutable, ensuring data integrity and preventing accidental changes.

Code Example

namespace CSharp13RecordsDTOs.Dtos;
public record CustomerDto(string FirstName, string LastName, string Email);
// Compilation error: Properties cannot be modified directly.
// errorCustomer.FirstName = "Ziggy";

To create mutable records.

namespace CSharp13RecordsDTOs.Dtos;
public record MutableCustomerDto
{
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
    public string EmailAddress { get; set; } = string.Empty;
    public string Name() => $"{FirstName} {LastName}";
    public string PersonDetails() => $"Name: {Name()}, Email: {EmailAddress}";
}
// Program.cs
using CSharp13RecordsDTOs.Dtos;
try
{
    var customer = new MutableCustomerDto
    {
        FirstName = "Ziggy",
        LastName = "Rafiq",
        EmailAddress = "[email protected]"
    };
    Console.WriteLine(customer.PersonDetials());
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

The Automatic Generation of Methods

The records automatically generate essential methods.

  • Using equality comparisons, compare records based on their values.
  • Extract record fields efficiently through deconstruction.
  • Provide a clear string representation using ToString.

Code Example. Equality Comparison.

var customer1 = new CustomerDto(
    "Ziggy", 
    "Rafiq", 
    "[email protected]"
);
var customer2 = new CustomerDto(
    "Ziggy", 
    "Rafiq", 
    "[email protected]"
);
Console.WriteLine(customer1 == customer2);

Code Example. Deconstruction.

namespace CSharp13RecordsDTOs.Dtos;
public record CustomerDto(string FirstName, string LastName, string Email)
{
    // The Deconstruct method should be explicitly defined
    public void Deconstruct(out string name, out string email)
    {
        name = $"{FirstName} {LastName}";
        email = Email;
    }
}
var customer1 = new CustomerDto("Ziggy", "Rafiq", "[email protected]");
var (name, email) = customer1;
Console.WriteLine($"Name: {name}, Email: {email}");

Cleaner Syntax through Positional Records

A succinct syntax is available in C# for defining records.

namespace CSharp13RecordsDTOs.Dtos
{
    public record OrderDto(int OrderId, decimal Amount, DateTime OrderDate);
}

Usage in a Service Layer

namespace CSharp13RecordsDTOs.Dtos
{
    public record ProductDto(Guid Id, string Name, string Description, decimal Price);
}
using CSharp13RecordsDTOs.Dtos;
namespace CSharp13RecordsDTOs.Services
{
    public class ProductService
    {
        public ProductDto GetProductById(Guid id)
        {
            // A simulation of fetching data from a database
            return new ProductDto(
                id, 
                "Mobile Phone", 
                "This is a new Mobile Phone, which has support for all platforms.", 
                195.99M
            );
        }
    }
}
var service = new ProductService();  
var product = service.GetProductById(  
    Guid.Parse("2b913c14-65a3-4b70-947c-bf3ab42d24c7")  
);  
Console.WriteLine(product);  

Summary

DTOs can be defined in a powerful, concise, and maintainable way with C# records, introduced in version 9 and further improved in later versions, including C# 13. Because of their immutability, automatic method generation, and clean syntax, they are ideal for scenarios that emphasize data integrity and simplicity.

Next time you're defining a simple data model, consider using records to harness their full potential by using records for DTOs.

You can access the code examples of this article on Ziggy Rafiq Repository: https://github.com/ziggyrafiq/CSharp13RecordsDTOs also if you found this article useful please smash the like button and become a friend or follow Ziggy Rafiq on C# Corner.

Up Next
    Ebook Download
    View all
    Learn
    View all
    Capgemini is a global leader in consulting, technology services, and digital transformation.