required 是编译期强制对象初始化器中显式赋值的语法检查,仅作用于 new typename { prop = value } 语法,不适用于构造函数或运行时反序列化,且要求属性必须有可访问的 set 访问器。

required 关键字不是运行时防护,也不是构造函数参数约束,它只在编译期强制对象初始化器中必须显式写出对应成员赋值——漏一个就报 CS8852,不写就不让过编译。
required 必须配合对象初始化器使用
它只认 new TypeName { Prop = value } 这种语法路径。哪怕你写了完整构造函数并在里面赋了值,只要初始化时没走 { },依然报错。
- ✅ 合法:
var p = new Person { Name = "Alice", Age = 30 }; - ❌ 编译失败:
var p = new Person(); p.Name = "Alice";(两步分开) - ❌ 编译失败:
var p = new Person("Alice", 30);(即使构造函数里写了this.Name = name;) - ❌ 编译失败:
var p = new Person();(无参构造 + 没初始化器 → 直接 CS8852)
根本原因:required 是编译器对“初始化表达式”的静态语法检查,不追踪构造函数逻辑流。
required 属性的 set 访问器有硬性限制
它要求属性必须带可访问的 set,且不能是 init、只读或字段。很多人写 public required string Name { get; init; } 结果报 CS8853,就是踩了这条。
- ✅ 允许:
public string Name { get; set; }、public string Name { get; private set; } - ❌ 禁止:
public string Name { get; init; }、public string Name => _name;(无 set)、public readonly string Name;(字段) - ⚠️ 可见性不能降级:public class 里不能有
protected required string Name,否则编译报错
这是因为 required 依赖编译器生成的隐式初始化逻辑,而 init 在对象创建后即封禁写入,破坏了该契约。
想用构造函数绕过 required?得加 [SetsRequiredMembers]
普通构造函数不会自动满足 required 要求。只有打上 [SetsRequiredMembers] 特性,编译器才认可该构造函数“已覆盖全部 required 成员初始化”。
- ✅ 合法:
[SetsRequiredMembers] public Person(string name, int age) { Name = name; Age = age; } - ⚠️ 注意:这个特性只是向编译器“断言”,不校验你是否真赋了值。如果漏写
Age = age;,运行时仍是0,毫无提示 - ✅ record 主构造函数自动加该特性:
public record Person(string Name, int Age);→Name和Age默认就带 required 语义
别指望靠构造函数“悄悄绕过”required;要么老实用对象初始化器,要么明确加特性并自己担责。
反序列化和反射完全无视 required
这是最容易被忽略的盲区:required 是纯编译期检查,JsonSerializer.Deserialize<person>(json)</person> 或 Activator.CreateInstance<person>()</person> 都能成功,得到的实例里 required 成员是 null 或 default,不抛异常也不警告。
- Web API 接收 JSON → DTO 有 required → 实际仍可能拿到未初始化实例
- 配置文件绑定(如
IConfiguration.Bind())同样跳过 required 校验 - 若需运行时保障,得额外加数据注解(如
[Required])或手动验证
换句话说:required 只管你“写代码时有没有填”,不管“运行时数据从哪来、是不是空”。它防的是手误,不是恶意输入或框架行为。










