C# init-only setters方法 C#如何创建只能在初始化时赋值的属性

来源:这里教程网 时间:2026-02-21 17:40:31 作者:

init-only setter 是什么,和 readonly 有什么区别

init
访问器是 C# 9 引入的语法糖,它允许属性在对象初始化期间(构造函数、对象初始化器、with 表达式)被赋值一次,之后不可修改。它不是运行时强制的只读,而是编译器层面的约束:只要赋值发生在「初始化上下文」中就合法,否则报错
CS8852

它和

readonly
字段不同:
readonly
字段只能在声明或构造函数里赋值,而
init
属性支持对象初始化器写法,更灵活;但它又比
set
更安全,避免了后续意外修改。

常见错误现象:

用对象初始化器赋值后,在构造函数外再赋值 → 编译失败,提示
CS8852
在构造函数里调用
this.Property = value
→ 合法(属于初始化上下文)
Init
方法里赋值 → 不合法,哪怕这个方法紧接在构造之后调用

怎么写一个带 init-only setter 的属性

语法就是把

set
换成
init

public class Person
{
    public string Name { get; init; }
    public int Age { get; init; } = 0;
}

关键点:

init
可以和
get
组合,也可以单独存在(但没意义)
可以有默认值(如
= 0
),它会在初始化器未提供值时生效
如果类有自定义构造函数,仍可使用对象初始化器 —— 编译器会把初始化器代码合并进构造调用 不支持在
init
访问器里写逻辑(比如验证),因为它是隐式实现的;如需逻辑,得用私有 backing field + 手动
init
实现

init-only 属性配合 with 表达式做不可变更新

with
是 C# 10 引入的特性,专为
init
属性设计,用于创建副本并修改部分字段:

var p1 = new Person { Name = "Alice", Age = 30 };
var p2 = p1 with { Age = 31 }; // ✅ 合法:生成新实例,Age 赋新值,Name 复制旧值

注意限制:

with
只能用于所有属性都有
init
get
的类型(即“记录式”行为)
如果某个属性只有
get
没有
init
with
不能改它,但可以改别的
with
不触发任何 setter/init 逻辑 —— 它是编译器直接复制字段/属性值,跳过访问器

init-only setter 的实际适用场景和坑

适合建模「创建后不变」的数据载体,比如 DTO、配置快照、事件载荷、API 响应模型。

容易踩的坑:

误以为
init
能防止反射修改 —— 实际上
PropertyInfo.SetValue()
依然能绕过,它只是编译期检查
在继承链中,基类
init
属性无法被派生类的构造函数「再次初始化」,除非显式调用
base(...)
并在初始化器中赋值
JSON 反序列化(如 System.Text.Json)默认不支持
init
属性赋值,需开启
PropertyNameCaseInsensitive = true
IncludeFields = true
等配置,或改用 Newtonsoft.Json(需
[JsonConstructor]
配合)
EF Core 6+ 支持
init
属性映射,但迁移生成可能出错,建议显式标注
[DatabaseGenerated(DatabaseGeneratedOption.None)]

init-only setter 看似简单,但它的边界全由编译器定义,而不是运行时保护。一旦离开初始化上下文(哪怕只是多包一层方法调用),赋值就会失败 —— 这个「上下文」的判定规则,比看起来更微妙。

相关推荐

热文推荐