✦ 为什么需要 Record
在 [[record]] 登场之前,C# 开发者定义一个纯数据载体(比如 [[dto]]、配置项、DDD 值对象)时,面临一个尴尬的现实:[[reference-equality]] 是默认行为。
两个对象哪怕每个字段都一模一样,只要它们在堆上的地址不同,== 就返回 false。为了实现「数据相同即相等」,你必须手动重写 Equals()、GetHashCode()、== 和 != 操作符——繁琐、易错、毫无技术含量。
[[record]] 的诞生就是为了一劳永逸地干掉这套样板代码。
✦ Record 的本质
[[record]] 本质上依然是 class 或 struct,但编译器会在 IL 层面自动为你生成:
- 基于所有公共属性的 [[value-equality]]
GetHashCode()的正确重写- 一个 JSON 风格的
ToString()重写 - 用于 [[with-expression]] 的克隆方法
你写的是一行声明,编译器产出的是一个功能完备的数据类型。
✦ record class vs record struct
✦ record class(引用类型)
1 | public record User(string Name, int Age); |
- 分配在托管堆上,受 [[gc]] 管理
- 使用 [[positional-syntax]] 时,属性默认是 [[init-only]](只读)
- 支持继承,可以参与多态
- 适合场景:数据体量较大、生命周期较长、需要序列化传输的对象
✦ record struct(值类型)
1 | public readonly record struct Point(int X, int Y); |
- 分配在栈上,不受 GC 扫描
- 使用 [[positional-syntax]] 时,属性默认是
get/set(可读可写) - 强烈建议永远写成
readonly record struct——可变的值类型在集合或字典中修改属性时极易引发隐蔽 Bug - 适合场景:坐标点、游戏数学向量、金融高频交易中需要 Zero-Allocation 的极端场景
✦ 四大核心特性
✦ 一行定义(位置语法)
1 | // 传统 class 需要:字段 + 属性 + 构造函数 + Equals + GetHashCode |
编译器自动生成带参构造函数和只读属性。
✦ 开箱即用的值相等性
1 | var a = new User("Alice", 25); |
[[value-equality]] 是 [[record]] 的默认行为,无需任何额外代码。
✦ 非破坏性突变(with 表达式)
1 | var user1 = new User("Alice", 25); |
[[with-expression]] 在函数式编程和不可变数据流中极其常用:你永远不修改原始数据,而是基于它派生新版本。
✦ 自动格式化输出
1 | var user = new User("Bob", 30); |
调试时直接打印对象就能看到所有字段值,省去手动重写 ToString() 的功夫。
✦ 实践决策树
用 record 还是 class?
- 对象的核心职责是装载数据,且经常需要比较或拷贝 → 无脑
record - 对象的核心职责是封装行为与状态(Service、Manager、Controller)→ 传统
class
用 record class 还是 record struct?
- 90% 的业务场景 → 直接
record class(简写record),现代 GC 完全兜得住 - 极端性能场景(游戏引擎、高频交易、底层框架)→
readonly record struct,追求 Zero-Allocation
record struct 防坑指南
永远写成 readonly record struct。可变值类型在 C# 中是公认的陷阱源——你以为修改了字典里的结构体,实际上修改的是栈上的副本。
✦ 底层逻辑
[[record]] 的魔法并非运行时黑科技,而是编译器在编译期完成的代码生成。当你写下 public record User(string Name, int Age);,编译器实际生成了:
- 一个带有
Name和Age属性的类 - 一个接受
string name, int age参数的主构造函数 - 重写的
Equals(object)和Equals(User)方法 - 重写的
GetHashCode()方法 - 重写的
ToString()方法 - 一个
<Clone>$()方法供 [[with-expression]] 使用 - 一个
IEquatable<User>接口实现
这一切都在 IL 层面静默完成,你无需看见,也无需维护。