C# Records Explained: Immutable Data Models and Value-Based Equality
Records are a modern C# feature designed for working with data-focused objects. They provide convenient value-based equality, concise syntax, readable object creation, and useful support for immutable data.
What is a Record in C#?
A record is a reference type by default that is designed primarily to represent data. Unlike traditional classes, records compare their values rather than only comparing object references.
public record Person(string Name, int Age);
Person person = new Person("Alice", 25);
Console.WriteLine(person);
Why Use Records?
- Represent data-focused objects clearly
- Provide value-based equality
- Reduce boilerplate code
- Support immutable-style programming
- Make data transfer models easier to define
- Provide convenient non-destructive object updates
Record Positional Syntax
The positional record syntax allows properties and the primary constructor to be declared in a single concise statement.
public record Product(
int Id,
string Name,
decimal Price
);
Product product = new Product(101, "Laptop", 75000m);
Value-Based Equality
Two record instances containing the same values are considered equal. This differs from the default equality behavior of ordinary classes.
Person first = new Person("Alice", 25);
Person second = new Person("Alice", 25);
Console.WriteLine(first == second);
public record Person(string Name, int Age);
Records vs Classes
| Feature | Record | Class |
|---|---|---|
| Primary Purpose | Data-oriented modeling | General object-oriented behavior |
| Equality | Value-based by default | Reference-based by default |
| Concise Syntax | Yes | Usually requires more code |
| Immutability | Easy to model | Must be designed explicitly |
| Common Use | DTOs and data models | Services and complex behavior |
Immutable Properties
Positional records expose init-only properties, which means their values can normally be assigned during object creation or initialization but not changed through ordinary property assignment afterward.
public record User(string Name, string Email);
User user = new User("John", "john@example.com");
// user.Name = "David";
// Not allowed after initialization
The with Expression
The with expression creates a new record instance based on an existing instance while changing selected properties.
public record User(string Name, string Role);
User user = new User("Alice", "Developer");
User updatedUser = user with
{
Role = "Senior Developer"
};
Console.WriteLine(user.Role);
Console.WriteLine(updatedUser.Role);
Record Inheritance
Record classes can participate in inheritance, allowing related data models to share common members.
public record Person(string Name);
public record Employee(
string Name,
int EmployeeId
) : Person(Name);
Employee employee = new Employee("Alice", 1001);
Record Classes
The record class syntax makes it explicit that the record is a reference type.
public record class Customer
{
public int Id { get; init; }
public string Name { get; init; } = "";
}
Customer customer = new Customer
{
Id = 1,
Name = "Alice"
};
Record Structs
C# also supports record structs. Unlike record classes, record structs are value types and can be useful for small data values.
public record struct Point(int X, int Y);
Point point = new Point(10, 20);
Console.WriteLine(point);
Record Struct vs Record Class
| Feature | Record Class | Record Struct |
|---|---|---|
| Type | Reference type | Value type |
| Default Equality | Value-based | Value-based |
| Nullability | Can be null | Cannot normally be null |
| Typical Use | Data models | Small value-like data |
Records and Deconstruction
Positional records provide convenient deconstruction, allowing their values to be assigned to separate variables.
public record Person(string Name, int Age);
Person person = new Person("Alice", 30);
var (name, age) = person;
Console.WriteLine(name);
Console.WriteLine(age);
Records and Pattern Matching
Records work naturally with C# pattern matching, making them useful when applications need to inspect data and perform different operations based on its shape.
public record Order(int Id, decimal Total);
Order order = new Order(1001, 2500m);
if (order is Order { Total: > 1000 })
{
Console.WriteLine("Large order");
}
Records for DTOs
Records are often useful for data transfer objects because they make the structure of data clear and can provide value-based equality.
public record UserResponse(
int Id,
string Name,
string Email
);
UserResponse response = new UserResponse(
10,
"Alice",
"alice@example.com"
);
Common Record Use Cases
- API request and response models
- Configuration data
- Immutable application data
- Value objects
- Messages between application components
- Data transfer objects
- Small value-based models
Common Mistakes to Avoid
- Using records for every type in an application
- Assuming records make every referenced object deeply immutable
- Using record structs for large or complex data
- Changing mutable collections inside otherwise immutable records
- Choosing records when identity-based behavior is more important than value equality
Record Best Practices
- Use records for data-centric types
- Prefer immutable properties when appropriate
- Use with expressions for non-destructive updates
- Choose record classes or record structs based on value/reference semantics
- Keep record models focused on representing data
- Use normal classes when object identity and behavior are central
Real-World Applications
- REST API models
- Application configuration
- Financial value objects
- User profile data
- Event messages
- Search result models
- Immutable application state
Practice Exercises
- Create a Person record
- Compare two records using value equality
- Update a record using with
- Create a record with multiple properties
- Build a record-based API response model
- Create a record struct for coordinates
- Compare records and classes in the same application
Conclusion
C# records provide a concise way to model data while offering value-based equality and convenient immutable-style updates. They are especially useful for DTOs, messages, value objects, and other data-focused types where the contents of an object matter more than its identity.