In modern C# development, Data Transfer Objects (DTOs) are everywhere — transporting data between APIs, services, and databases.
Traditionally, we’ve defined them as classes:
public sealed class ProductResponse
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string? Brand { get; set; }
}
It works, but it comes with extra boilerplate and mutable state we often don’t need.
With C# 9, we now have a better option: record types.
What Is a Record?
A record is a special kind of reference type in C# designed for immutable data and value-based equality.
Key features:
Immutable by default — values can only be set at creation time.
Value-based equality — two records with the same values are considered equal.
Concise syntax — define all properties in a single line.
Example:
public sealed record ProductResponse(
Guid Id,
string Name,
decimal Price,
string? Brand);
This one-liner replaces the verbose class version and makes your DTOs safer and cleaner.
Why Records Are Great for DTOs
1. Encourages Immutability
Most DTOs are read-only after creation. Records enforce this when using positional parameters.
var product = new ProductResponse(Guid.NewGuid(), "Laptop", 1299.99m, "Contoso");
// product.Price = 999.99m; ❌ Compile-time error
No accidental property changes after initialization.
2. Value-Based Equality
With classes, two objects with the same values are not equal unless you manually override Equals
and GetHashCode
.
Records solve this automatically:
var p1 = new ProductResponse(Guid.Parse("11111111-1111-1111-1111-111111111111"), "Laptop", 1299.99m, "Contoso");
var p2 = new ProductResponse(Guid.Parse("11111111-1111-1111-1111-111111111111"), "Laptop", 1299.99m, "Contoso");
Console.WriteLine(p1 == p2); // True ✅
3. Less Boilerplate, More Clarity
With classes, every property must be explicitly written.
With records, you can define everything inline — making DTOs shorter and easier to maintain.
Alternative Syntax
If you prefer named properties over positional parameters, you can still use records:
public sealed record ProductResponse
{
public Guid Id { get; init; }
public string Name { get; init; }
public decimal Price { get; init; }
public string? Brand { get; init; }
}
Same benefits, just a different style.
When Not to Use Records
Records are not always the right choice:
Your DTO needs to be mutable after creation.
You’re using C# 8 or earlier.
Your team prefers explicit property declarations.
Final Thoughts
DTOs are about moving data cleanly and safely.
C# records fit this role perfectly — concise, immutable, and equality-friendly.
If you’re still writing verbose class-based DTOs, try records in your next project.
You’ll write less code, avoid accidental bugs, and keep your data models cleaner.
I hope you found this guide helpful and informative.
Thanks for reading!
If you enjoyed this article, feel free to share it and follow me for more practical, developer-friendly content like this.