一、基本功能介绍
Record 是C# 9.0引入的类型修饰符,用于创建数据驱动的类型,编译器自动合成大量成员以简化数据模型开发。分为两种核心类型:
| 类型 | 声明语法 | 本质 | 特性 |
|---|---|---|---|
| Record Class | record 或 record class |
引用类型 | 位置参数生成init-only属性,默认不可变 |
| Record Struct | record struct |
值类型 | 位置参数生成读写属性,支持值语义 |
核心语法示例
csharp
// 1. 位置记录(最常用)
public record Person(string FirstName, string LastName);
// 2. 带自定义成员的记录
public record Person
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public int Age { get; init; } = 18; // 默认值
}
// 3. 只读值类型记录
public readonly record struct Point(double X, double Y, double Z);
编译器自动合成的核心成员
- 主构造函数:与位置参数匹配的构造函数
- 属性 :位置参数对应的公共属性(
record class为init-only,record struct为读写) - 值相等性 :
Equals(T other)、==/!=运算符,基于属性值比较 - 解构方法 :
Deconstruct(out T1 prop1, out T2 prop2, ...) - ToString:格式化字符串,显示类型名和属性值
- 复制构造函数 :支持
with表达式的非破坏性复制
二、设计原理深度剖析
1. 核心设计目标
- 简化数据模型:减少样板代码,专注数据定义而非成员实现
- 值语义优先:基于值而非引用比较相等性,适合数据传输场景
- 不可变友好:默认不可变,提升线程安全和可预测性
- 继承支持:记录可继承自其他记录,构建层次化数据模型
2. 与类/结构体的关键区别
| 特性 | Record Class | Class | Record Struct | Struct |
|---|---|---|---|---|
| 相等性 | 值相等(基于属性) | 引用相等 | 值相等(基于属性) | 值相等(基于反射) |
| 位置参数 | 支持,生成init-only属性 | 不支持 | 支持,生成读写属性 | 不支持 |
| 继承 | 可继承自其他记录 | 可继承自任意类 | 可继承自其他记录结构体 | 不可继承 |
| 默认可变性 | 不可变(init-only) | 可变 | 可变(读写) | 可变 |
| 解构方法 | 自动生成 | 需手动实现 | 自动生成 | 需手动实现 |
3. 不可变性与非破坏性复制
- 浅不可变性 :
record class的位置参数属性为init-only,初始化后不可直接修改 - 引用类型的浅不可变限制:引用类型属性的内部数据仍可修改(如数组元素)
- 非破坏性复制 :通过
with表达式创建新实例,复制原实例并修改指定属性
csharp
Person person1 = new("Nancy", "Davolio");
Person person2 = person1 with { FirstName = "John" }; // 复制并修改
4. 继承与相等性契约
- 继承规则:记录只能继承自其他记录,类不能继承自记录,反之亦然
- EqualityContract :编译器合成
protected override Type EqualityContract { get; },确保派生记录的相等性比较基于运行时类型 - ToString格式化 :基类
PrintMembers方法被调用,包含所有基类和派生类的属性
三、生产环境使用场景
1. 最佳适用场景
- 数据传输对象(DTO):API请求/响应、消息队列数据,简化序列化/反序列化
- 领域模型:值对象、不可变的业务实体,确保数据一致性
- 配置模型:应用配置、系统参数,支持不可变配置加载
- 状态管理:函数式编程中的状态对象,避免副作用
- 测试数据:单元测试中的测试数据对象,简化数据构建
2. 不适用场景
- 实体框架核心(EF Core)实体:依赖引用相等性,记录不适合
- 频繁修改的对象:如业务实体的状态频繁变更,类更适合
- 需要自定义相等性逻辑:需覆盖合成的相等性方法,增加复杂度
- 性能敏感的高频创建对象:记录的合成成员有轻微性能开销
3. 企业级实践示例
场景1:API响应模型
csharp
// 定义响应记录
public record ApiResponse<T>(int StatusCode, string Message, T Data);
// 使用
var response = new ApiResponse<User>(200, "Success", new User { Id = 1, Name = "John" });
Console.WriteLine(response); // 自动格式化输出
场景2:分层数据模型
csharp
// 基记录
public abstract record Entity(int Id);
// 派生记录
public record User(int Id, string Name, string Email) : Entity(Id);
public record Product(int Id, string Name, decimal Price) : Entity(Id);
// 相等性验证
var user1 = new User(1, "John", "john@example.com");
var user2 = new User(1, "John", "john@example.com");
Console.WriteLine(user1 == user2); // True(值相等)
场景3:非破坏性复制
csharp
// 订单记录
public record Order(int Id, string CustomerName, decimal Total, DateTime OrderDate);
// 创建订单
var order = new Order(1, "Nancy Davolio", 99.99m, DateTime.Now);
// 修改部分属性创建新订单
var updatedOrder = order with { Total = 129.99m, OrderDate = DateTime.Now.AddDays(1) };
四、版本演进与最新特性(2026)
- C# 10 :引入
record struct,支持值类型记录 - C# 12:优化记录性能,减少内存开销
- C# 14:改进记录的泛型支持和编译器优化
- C# 15(预览):增强记录的模式匹配和与联合类型的集成
五、总结与最佳实践
- 选择建议 :
- 优先使用
record class定义引用类型数据模型 - 性能敏感的值类型场景使用
record struct - 简单数据结构优先考虑位置记录语法
- 优先使用
- 性能考量 :
- 高频创建的轻量级数据对象考虑使用结构体
- 大型记录结构注意内存占用,合理拆分
- 可维护性 :
- 为复杂记录添加XML文档注释
- 统一记录命名规范,便于团队协作
- 避免过度继承,保持层次简单