← 中文资源

简体中文版本

C# NullReferenceException — 开启可空分析并找到空值来源

通过堆栈定位解引用点,启用可空引用类型与警告即错误,修复未初始化依赖、LINQ 默认值和异步返回空值的问题。

阅读英文原文

现象

抛出 System.NullReferenceException: Object reference not set to an instance of an object.;堆栈指向访问属性或调用方法的那一行;只在生产环境或配置变更后出现;项目未启用可空引用类型警告。

诊断

先启用可空分析,让编译器指出可能为空的流转路径:

dotnet build -p:Nullable=enable -p:WarningsAsErrors=nullable
dotnet test --logger "console;verbosity=detailed" 2>&1 | grep -A20 'NullReferenceException'
dotnet format analyzers --verify-no-changes --severity warn 2>&1 | head -40

堆栈中第一个业务代码帧就是解引用点,但空值可能来自更早的依赖注入失败、FirstOrDefault 无匹配或异步方法返回 null。重复出现的 CS8602 警告集中在某个类,通常意味着字段未初始化或构造函数路径遗漏。启动时若提示依赖无法解析,应检查 DI 注册,并在开发环境开启 ValidateOnBuild。

修复

在项目级启用可空引用类型,并把可空警告视为错误;公共入口用 ArgumentNullException.ThrowIfNull 校验,查询结果显式判断空值,集合与依赖在构造时初始化:

public OrderService(IOrderRepository repository)
{
    _repository = repository
        ?? throw new ArgumentNullException(nameof(repository));
}

public decimal Total(Order order)
{
    ArgumentNullException.ThrowIfNull(order);
    return order.Lines.Sum(line => line.Price * line.Quantity);
}

不要用 null-forgiving 运算符 ! 压掉警告,那只是隐藏风险;也不要把所有属性都改成可空,那会让契约变得模糊。

验证

在可空警告视为错误的配置下重新构建,确认解引用处的警告已消失、流程可证明非空。运行覆盖失败路径的测试,再复现原生产场景,异常应不再出现,或变为明确指出参数名的 ArgumentNullException,后者说明边界校验已经生效。