When building APIs, message contracts, or internal application boundaries, DTOs are everywhere. Traditionally, DTOs have been implemented as mutable classes, but since C# 9, records offer a far better abstraction for this use case. In this post, I’ll explain why I almost always prefer public sealed record for DTOs.
DTOs are data, not behavior
DTOs exist to move data between layers or systems. They are not domain entities, and they typically don’t contain behavior.
That makes them a perfect fit for immutable, value-based types.
Value-based equality by Default
With records, equality is based on the values of the properties, not object identity.
This means two DTO instances containing the same data are considered equal, which is often exactly what we want when:
- Comparing API responses
- Validating messages
- Writing unit tests
- Using DTOs as dictionary keys or cache entries
public sealed record OrderDto(
Guid OrderId,
Guid CustomerId,
decimal TotalAmount
);
Two instances with the same values will compare as equal without any boilerplate code.
Why sealed Matters
By default, records support inheritance. For DTOs, this is rarely desirable.
Marking a record as sealed:
- Prevents accidental inheritance
- Guarantees stable equality semantics
- Makes intent explicit: this is a closed data contract
For API and messaging scenarios, this stability is critical.
Immutability Encourages Safer Code
Records are designed around immutability. Once created, their values don’t change.
This leads to:
- Fewer side effects
- Easier reasoning about code
- Safer concurrent execution
- More predictable behavior in distributed systems
Clear Intent in Code
public sealed record communicates intent immediately:
“This type is a data-only, immutable, value-based contract that is not meant to be extended.”
That clarity helps both the compiler and future developers.
Conclusion
For DTOs in modern C#, public sealed record is often the best possible choice:
- Less boilerplate
- Safer equality
- Clear intent
- Better defaults
I still use classes where identity, mutability, or behavior matters, but for DTOs, records have become my default.