现代C#如何兼顾字段强制初始化、反序列化与非空要求?
现代C#中满足构造安全、反序列化兼容与非空字段的优雅实现
核心需求回顾
- 强制类使用者初始化所有必填字段,不能遗漏
- 兼容主流反序列化器(如System.Text.Json、Newtonsoft.Json)填充字段,不受私有成员限制
- 所有引用类型字段默认非空,避免null引用
一、C# 11+ / .NET 7+ 最优方案:required修饰符+默认初始化
C#11引入的required修饰符完美适配需求1,配合字段默认初始化满足需求3,同时对反序列化器友好(System.Text.Json从.NET7开始原生支持)。
示例代码:
public class MyClass { // 必填字段,编译器强制构造/对象初始化时赋值,遗漏直接报错 public required bool Field1 { get; init; } // 非空默认值,如果需要使用者显式赋值就保留required,否则可去掉 public IEnumerable<bool> Field2 { get; init; } = new List<bool>(); // 可选字段,但默认非空,避免null public List<string> Field3 { get; init; } = new(); }
- 需求1满足:编译期强制检查,使用者无法创建未初始化必填字段的实例
- 需求2满足:
initsetter对反序列化器完全开放,System.Text.Json、Newtonsoft.Json都能正常赋值 - 需求3满足:所有引用类型字段都有默认实例,不会出现null
解决新增字段遗漏问题
- 新增
required字段时,编译器会直接报错提醒所有使用者更新初始化代码 - 反序列化时配置
RequiredMemberHandling.Error,缺少必填字段会抛出异常,避免静默遗漏:
var options = new JsonSerializerOptions { RequiredMemberHandling = RequiredMemberHandling.Error, PropertyNameCaseInsensitive = true }; // 反序列化缺字段直接抛JsonException var obj = JsonSerializer.Deserialize<MyClass>(json, options);
二、.NET 6(C#10)兼容方案:init setter+构造函数重载+静态分析
由于C#10没有required修饰符,结合构造函数和静态分析实现需求:
方案1:全参构造+私有无参构造+init setter
public class MyClass { private bool _field1; private IEnumerable<bool> _field2 = new List<bool>(); // 公共全参构造,强制使用者传入必填字段 public MyClass(bool field1) { _field1 = field1; } // 供反序列化用的私有无参构造,标记[JsonConstructor]避免歧义 [JsonConstructor] private MyClass() { } public bool Field1 { get => _field1; init => _field1 = value; } public IEnumerable<bool> Field2 { get => _field2; init => _field2 = value ?? new List<bool>(); } }
- 需求1满足:公共构造强制传参,使用者无法创建未初始化实例
- 需求2满足:私有无参构造配合
initsetter,反序列化器可正常实例化赋值 - 需求3满足:字段默认初始化+
initsetter的null检查,确保非空
避免新增字段遗漏
- 项目开启
<Nullable>enable</Nullable>,让编译器检查所有引用类型的非空性 - 启用Roslyn代码分析器(如CA1062),检查构造函数是否覆盖所有必填字段
- 配置反序列化器的必填字段检查,缺字段直接抛异常
替代方案:使用记录类型(Record)
.NET6的记录类型支持位置参数,强制初始化,同时兼容反序列化:
public record MyClass(bool Field1, IEnumerable<bool> Field2) { // 给Field2设置默认值,确保非空 public IEnumerable<bool> Field2 { get; init; } = Field2 ?? new List<bool>(); }
- 需求1满足:位置参数强制传参,遗漏编译报错
- 需求2满足:记录的
init属性兼容反序列化 - 需求3满足:默认值避免null
- 新增字段时,修改记录位置参数会强制所有使用者更新代码,避免遗漏
三、通用优化:统一反序列化配置
不管哪个版本,都可以通过配置强化必填字段检查:
- System.Text.Json:设置
RequiredMemberHandling = RequiredMemberHandling.Error - Newtonsoft.Json:设置
MissingMemberHandling = MissingMemberHandling.Error,配合JsonProperty(Required = Required.Always)标记必填字段
最后总结下:
- C#11+/.NET7+直接用
required修饰符,简洁且编译期安全 - .NET6/C#10用全参构造+
initsetter,或记录类型,配合静态分析和反序列化配置 - 新增字段时靠编译器检查和反序列化报错,杜绝静默遗漏
内容的提问来源于stack exchange,提问作者jeancallisti
相关产品推荐
相关产品推荐

