Keywords: C# Record | Data Type Selection | Immutability | Value Semantics | Performance Optimization
Abstract: This article provides an in-depth analysis of the Record type introduced in C# 9.0, comparing it with traditional Class and Struct types. By explaining the differences between value types and reference types, and highlighting Record's immutability and value semantics, the article offers practical guidance for selecting appropriate data types in real-world development. It focuses on Record's advantages in scenarios like DTOs and API request bindings, demonstrates its copying mechanisms through code examples, and discusses performance considerations to help developers make informed technical decisions.
Introduction
With the introduction of record in C# 9.0, developers now have a new way to model data. Compared to traditional class and struct types, record offers unique advantages in specific scenarios. This article aims to deeply analyze the core characteristics of these three types and provide practical selection guidelines to help developers make sound technical decisions in real projects.
Data Type Fundamentals: Value Types vs Reference Types
In C#, struct is a value type, while class and record are reference types. Value types are allocated on the stack and store data directly, while reference types are allocated on the heap and store references to data. This fundamental difference affects performance, memory management, and behavior.
For struct, Microsoft officially recommends using it when all the following conditions are met:
- Logically represents a single value, similar to primitive types (like int, double)
- Instance size is under 16 bytes
- Is immutable
- Will not have to be boxed frequently
If these conditions are not met, reference types should be considered.
Core Characteristics of Record
record is a reference type introduced in C# 9.0 but with value semantics. Its main features include:
- Default Immutability: Primary constructor parameters in
recordgenerate read-only properties by default - Value Equality: Equality comparison based on property values rather than reference addresses
- Concise Syntax: Supports positional records and
withexpressions
The following example demonstrates basic record usage:
public record Person(string FirstName, string LastName);
// Creating modified copies using with expressions
var original = new Person("John", "Doe");
var modified = original with { LastName = "Smith" };
Practical Application Scenarios
Data Transfer Objects (DTOs)
For DTOs that transfer data between controller and service layers, record is an ideal choice. Its immutability ensures data safety during transmission, while value semantics simplifies equality comparisons.
// Using record as DTO
public record SearchParameters(string Query, int PageSize, int PageNumber);
public class SearchController : Controller
{
public async Task<IActionResult> Search(SearchParameters parameters)
{
// parameters is immutable, ensuring data safety
var results = await _service.SearchAsync(parameters);
return Ok(results);
}
}
API Request Binding
In ASP.NET Core APIs, using record as request models ensures the immutability of request data, which is ideal for API design.
// Request binding example
public record CreateUserRequest(string Username, string Email, int Age);
[HttpPost]
public IActionResult CreateUser(CreateUserRequest request)
{
// request is immutable, preventing accidental modifications in subsequent processing
var user = _mapper.Map<User>(request);
_repository.Add(user);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
Copying Mechanisms Explained
The copying behavior of record requires special attention. When creating copies using with expressions:
- Value-type properties are copied
- Reference-type properties maintain the same reference (shallow copy)
The following example demonstrates copying behavior in different scenarios:
public record ExampleRecord(List<string> Items, int Count);
var original = new ExampleRecord(new List<string> { "a", "b" }, 2);
var copy = original with { Count = 3 };
// Modifying the original list affects the copy
original.Items.Add("c");
Console.WriteLine(copy.Items.Count); // Output: 3
Console.WriteLine(ReferenceEquals(original.Items, copy.Items)); // Output: True
Performance Considerations
While record provides convenient syntax and semantics, it should be used cautiously in performance-sensitive scenarios. Creating new instances with with expressions involves copying operations, which may incur performance overhead for large objects.
Benchmark tests show that traditional class may have slight performance advantages in scenarios requiring frequent creation of modified copies:
// Example benchmark results
// TestRecord: 120.19 μs
// TestClass: 98.91 μs
However, in most practical applications, this difference is usually negligible, especially when operations involve databases or file systems where external operation latency far exceeds memory copying overhead.
Selection Guidelines Summary
Based on the above analysis, the following selection guidelines can be summarized:
- When to use struct: When the data type meets all value type conditions, particularly for small, immutable, value-semantic data.
- When to use record:
- Immutable data transfer objects are needed
- API request/response models
- Any reference type requiring value semantics
- Unidirectional data flow scenarios
- When to use class:
- Mutable state is required
- Complex object hierarchies
- Identity rather than value equality is needed
- Performance-critical scenarios requiring frequent modifications
Conclusion
The introduction of record provides C# developers with a new tool particularly suited for handling immutable data models. In practical development, appropriate data types should be selected based on specific requirements: struct for lightweight value types, record for immutable reference type data, and class for traditional mutable object models. Understanding the core differences and application scenarios of these types will help write safer, more maintainable code.
As the C# language continues to evolve, new features like anonymous records will further enrich data modeling options. Developers should stay informed about language innovations while making reasonable technical choices based on specific project requirements.