C#泛型类型定义中修改可空性:实现兼容可空/非空入参的NotNull扩展方法
泛型可空语义适配的NotNull扩展方法实现
需求可行性结论
该需求完全可以实现,核心是利用C#可空引用类型(NRT)的编译期语义标注,在泛型签名层面拆分“入参侧的可空兼容性”和“返回侧的非空承诺”,不需要额外的变通逻辑。
原有方法的问题点
现有方法签名没有适配NRT的语义传递逻辑:当调用方传入属性类型为IEnumerable<string>?的规则构造器时,编译器会直接把TProperty推导为带可空标注的IEnumerable<string>?,导致返回的IRuleBuilder的泛型参数也保持可空标注,完全达不到“完成NotNull校验后,将属性上下文标记为非空”的效果。
具体实现方案
首先引入可空语义标注所需的命名空间:using System.Diagnostics.CodeAnalysis;
针对本次聚焦的引用类型场景,重构后的扩展方法签名如下:
public static IRuleBuilder<TModel, TProperty> NotNull<TModel, [NotNull] TProperty>( this IRuleBuilderInitial<TModel, TProperty?> ruleBuilder ) where TProperty : class { // 保留原有的NotNull校验注册逻辑即可 return ruleBuilder.SetValidator(new NotNullValidator<TModel, TProperty>())!; }
这套签名逻辑和FluentValidation 11版本之后内置的NotNull方法实现完全一致,是泛型场景下可空语义适配的标准实践,为什么能满足需求可以从两个维度拆解:
- 入参侧兼容性:因为
TProperty加了class约束(匹配引用类型场景),编译器会自动兼容两种入参情况:- 当传入的规则绑定的是可空引用类型
IEnumerable<string>?时,TProperty会被推导为对应的非空基础类型IEnumerable<string> - 当传入的规则绑定的本身就是非空引用类型
IEnumerable<string>时,因为非空引用类型天然兼容可空类型的参数位置,不会触发任何可空性警告
- 当传入的规则绑定的是可空引用类型
- 返回侧非空强制:方法返回值的泛型参数直接使用推导出来的非空
TProperty,因此无论入参是带不带?标注的IEnumerable<string>,最终返回的IRuleBuilder的属性泛型都会是不带可空标注的IEnumerable<string>,完全符合要求。
其中[NotNull]标注的作用是明确告诉编译器:这个方法执行完成后,对应的属性上下文已经被保证非空,后续链式调用的所有方法都不会再触发“可能为null”的编译警告。
扩展兼容
如果需要同时兼容值类型的可空NotNull场景,只需要重载一个带值类型约束的版本即可,不会和现有引用类型的方法冲突:
// 兼容int?、Guid?等可空值类型的NotNull重载 public static IRuleBuilder<TModel, TProperty> NotNull<TModel, TProperty>( this IRuleBuilderInitial<TModel, TProperty?> ruleBuilder ) where TProperty : struct { return ruleBuilder.SetValidator(new NotNullValidator<TModel, TProperty>())!; }
内容的提问来源于stack exchange,提问作者CheloXL
相关产品推荐
相关产品推荐

