启用Nullable Reference Type时DTO构造器的优化方案咨询
嘿,太懂你这种被重复代码折磨的感觉了——既要让DTO能被反序列化(空构造器+公共setter),又要满足可空引用类型的规则,还要防止别人随便手动实例化这些对象,写一堆null!初始化代码确实够难看的。下面给你几个实用的优化方案,根据你用的C#版本选就行:
1. 优先用C# 10+的required关键字(最推荐)
如果你的项目已经升级到C#10或更高版本,required属性简直是为这个场景量身定做的。它既能强制手动创建对象时必须赋值所有必填属性(防止别人随便new出来),又能让反序列化器正常工作(序列化器通过反射赋值,不受编译器强制检查的影响),而且完全不用写那些null!的初始化代码:
public class AddressRF { // 保留空构造器给反序列化用,不用写任何初始化代码 public AddressRF() { } public required string ZipCode { get; set; } public required string RegionCode { get; set; } public required Toponym RegionName { get; set; } public required Toponym District { get; set; } public required Toponym City { get; set; } public required Toponym Settlement { get; set; } public required Toponym Street { get; set; } public required Toponym House { get; set; } public required Toponym Bulk { get; set; } public required Toponym Flat { get; set; } public required string KladrCode { get; set; } public required string HouseRaw { get; set; } public required string BulkRaw { get; set; } public required string FlatRaw { get; set; } }
这样一来,有人想手动new AddressRF()会直接被编译器报错,必须给所有required属性赋值;而反序列化器(比如System.Text.Json或Newtonsoft.Json)能正常处理,完美满足你的需求。
2. 用C#9+的记录类型(Record Types)
如果你的DTO不需要太多自定义逻辑,记录类型是另一个简洁的选择。它自带值相等性,代码更紧凑,同样可以配合required关键字或者init-only属性:
方式一:带主构造器的记录(适合不需要空构造器的序列化场景)
public record AddressRF( string ZipCode, string RegionCode, Toponym RegionName, Toponym District, Toponym City, Toponym Settlement, Toponym Street, Toponym House, Toponym Bulk, Toponym Flat, string KladrCode, string HouseRaw, string BulkRaw, string FlatRaw );
这种方式连属性都不用自己写,但要注意:有些旧版本的反序列化器可能需要额外配置才能支持记录类型的主构造器。如果遇到问题,就用下面的方式:
方式二:带空构造器和required属性的记录
public record AddressRF { public AddressRF() { } public required string ZipCode { get; init; } public required string RegionCode { get; init; } // 其他属性同上,用init代替set还能防止对象创建后被修改 }
3. 针对C#8的折中方案
如果还在用C#8,没法用上面的新特性,那可以把null!直接移到属性声明里,比在构造器里写一堆初始化代码整洁一点:
public class AddressRF { public AddressRF() { } public string ZipCode { get; set; } = null!; public string RegionCode { get; set; } = null!; public Toponym RegionName { get; set; } = null!; // 其他属性同理... }
虽然还是要写null!,但至少不用在构造器里重复每个属性的赋值逻辑,代码结构更清晰。
额外小技巧:配置反序列化器跳过null检查
如果你用的是System.Text.Json,可以在配置里开启JsonSerializerOptions.PropertyNameCaseInsensitive = true(虽然和Nullable无关,但能让序列化更顺手);另外,确保你的序列化器版本支持这些新特性(比如.NET 6+才支持required属性的反序列化)。
备注:内容来源于stack exchange,提问作者Pasha

