C#9中init-only属性与只读属性有何区别?求解认知误区
搞懂C# 9的init-only属性 vs 传统只读属性
嘿,这个问题问得特别戳痛点——刚接触C# 9的开发者几乎都会混淆这俩特性,咱来掰扯清楚你忽略的点!
首先得肯定你的理解:你写的Thingy类里的Name和Description是传统的get-only只读属性,确实只能在构造函数内部赋值,对象创建后完全不可修改,这部分你说的一点没错。那init-only属性到底多了啥?
核心区别就是初始化的灵活性——它保留了不可变性,但给你提供了更多创建对象的方式,不用被构造函数绑死。
1. 对象初始化器的支持
传统只读属性必须通过构造函数传参来赋值,如果你想给类加几个可选属性,就得写一堆重载构造函数,特别麻烦。但init-only属性允许你用对象初始化器直接赋值,比如:
class Thingy { // 把原来的get;改成get; init; public string Name { get; init; } public string Description { get; init; } public string? ExtraInfo { get; init; } // 新增可选属性 public override string ToString() => $"{Name}: {Description} {ExtraInfo ?? ""}"; }
然后创建对象的时候,你可以灵活选择要赋值的属性:
var thingy1 = new Thingy { Name = "Test1", Description = "Full info" }; var thingy2 = new Thingy { Name = "Test2", Description = "Basic info", ExtraInfo = "Some extra" };
不用写两个构造函数,是不是清爽多了?
2. 继承与记录类型的适配
在继承场景里,子类如果想给父类的只读属性赋值,必须在子类构造函数里显式调用父类的构造函数传参。但用init-only属性的话,子类可以直接在对象初始化器里给父类属性赋值:
class BaseThing { public string Id { get; init; } } class DerivedThing : BaseThing { public string Name { get; init; } } // 直接初始化父类+子类属性 var derived = new DerivedThing { Id = "123", Name = "Test" };
另外,C# 9的记录类型(record)默认用init-only属性,配合with表达式可以轻松实现非破坏性修改——创建新对象的同时修改部分属性,原对象保持不变:
record Thingy { public string Name { get; init; } public string Description { get; init; } } var original = new Thingy { Name = "Test", Description = "Old desc" }; var updated = original with { Description = "New desc" }; // original还是原来的样子,updated是新对象
这是传统只读属性完全做不到的。
总结一下
| 特性 | 赋值时机 | 灵活性 |
|---|---|---|
| 传统get-only属性 | 仅构造函数内部 | 低,依赖构造函数重载 |
| init-only属性 | 构造函数/对象初始化器/with表达式 | 高,不用写冗余构造函数 |
你之前忽略的,就是init-only属性在不破坏不可变性的前提下,给对象创建带来的灵活性——它不是替代传统只读属性,而是补充了更现代的初始化方式,适配C#后续的记录类型、非破坏性修改等特性。
内容的提问来源于stack exchange,提问作者Mark A. Donohoe
相关产品推荐
相关产品推荐

