Data Transfer Objects (DTOs) define the data exchanged between application boundaries. C# records can make DTOs more concise and express value-oriented semantics, but they should be used deliberately rather than replacing every class in an application.
Create a concise DTO with a record
A record can represent a simple request:
public record CreateCustomerRequest(
string Name,
string Email);
This positional record generates properties and value-based equality behavior. It is often suitable for small data structures whose purpose is to carry values.
A traditional class remains appropriate when the type has complex mutable behavior, lifecycle requirements, or semantics that do not fit a record.
Use immutable-style properties
Records are useful when application code should not casually change the values after construction.
public record CustomerSummary(
int Id,
string Name,
string Email);
A record declared this way provides init-only properties. This prevents ordinary reassignment after initialization, but it does not make every referenced object deeply immutable.
For example, a record containing a mutable list can still expose a list whose contents change.
Use with expressions for modifications
Records support non-destructive updates:
var original = new CustomerSummary(
10,
"Alex",
"alex@example.com");
var updated = original with
{
Name = "Alex Morgan"
};
The updated record is a new instance with the changed property. The original remains unchanged.
This can be useful for transformations and value-oriented application logic. It should not be confused with an automatic database update.
Separate API contracts from domain models
An API request should not automatically expose the entire domain entity.
public record UpdateCustomerRequest(
string Name,
string Email);
The application can map the request to the appropriate domain operation, where business rules are enforced.
This separation avoids accidental exposure of internal properties and allows the API contract to evolve independently of the persistence model.
Validate incoming DTOs
DTOs describe data shape, but a type declaration alone does not guarantee that values are valid.
ASP.NET Core supports validation attributes and other validation approaches:
using System.ComponentModel.DataAnnotations;
public record CreateCustomerRequest(
[Required]
[StringLength(100)]
string Name,
[Required]
[EmailAddress]
string Email);
The application must ensure validation is actually executed and that errors are returned consistently. Business rules such as whether an email address is already registered require additional checks.
Understand equality semantics
Records compare values rather than relying only on object identity. That is useful for value-like data, but it can be surprising if a record represents an entity whose identity is more important than its current property values.
Choose records for value-oriented types and classes for types whose identity, mutable lifecycle, or behavior is central to the domain.
Conclusion
C# records provide concise syntax, value-based equality, and useful non-destructive update patterns. They work particularly well for DTOs and value objects, provided developers understand shallow immutability, validation requirements, and the distinction between API contracts and domain entities.