C# 9 record与带自动实现只读属性的类的不可变性差异
带只读自动属性的类与C# 9 Record的不可变性对比
基础不可变性的共性
你给出的两个示例在公开属性的不可变性层面没有本质区别:
- 普通类的
{ get; }只读自动属性,仅能在类的构造函数中赋值,实例构造完成后外部无法修改 - 默认声明的位置参数Record,编译器会为所有参数生成带
init访问器的公开属性,仅能在实例构造阶段(构造函数、对象初始化器)赋值,构造完成后外部无法修改
两者底层都由编译器生成私有只读支持字段,公开层面都满足不可变数据对象的契约。
不可变性相关的核心差异
1. 实例拷贝能力
Record由编译器自动生成with表达式支持,可以快速基于现有实例生成修改了指定属性的新不可变实例:
var oldColor = new RgbColor(255,0,0); var newColor = oldColor with { Green = 255 }; // 生成新实例,原实例不受影响
普通只读类要实现相同能力,需要手动编写拷贝构造函数、属性拷贝逻辑,开发成本更高。
2. 反序列化兼容性
两者反序列化完成后都具备完全的不可变性,不会被外部修改,但反序列化过程的兼容性存在差异:
- 普通只读类的属性仅能在构造函数中赋值,要求反序列化库必须严格匹配构造函数的参数名称、类型和数量,否则会直接反序列化失败
- Record的
init访问器允许反序列化库在构造实例后再完成属性赋值,对不同反序列化配置的兼容性更好,不会破坏最终的不可变性
Record针对不可变场景的额外增强
除了语法更简洁之外,Record默认提供了很多针对不可变数据场景的优化:
- 默认值相等性:两个Record实例只要所有属性值完全一致,就会被判定为相等,不需要手动重写
Equals、GetHashCode方法,非常适合不可变数据作为字典键、集合元素的场景 - 编译器层面的不可变契约保障:使用位置参数声明Record时,编译器默认生成
init属性,不会像手动编写普通类那样,意外漏掉只读声明引入可变属性 - 自动生成结构化的
ToString()方法:默认会输出所有属性的键值对,方便调试不可变对象的状态
注意:Record本身不强制不可变性,如果你手动为属性添加
set访问器,也可以实现可变的Record,只有默认位置参数声明的Record才会默认满足不可变契约。
内容的提问来源于stack exchange,提问作者andrei.ciprian
相关产品推荐
相关产品推荐

