📋 NRT 速查 Nullable Reference Types

C# 8 引入 · 编译时静态分析 · 无运行时开销 · 📖 完整课程

开启方式

方式写法作用范围
项目级<Nullable>enable</Nullable> in .csproj整个项目
文件级#nullable enable 在文件顶部当前文件
局部关闭#nullable disable从该行到文件尾(或 restore)
恢复项目设置#nullable restore回到 .csproj 的设定

核心语法 / 四个角色

写法名称含义编译器行为
string name 非 null 引用 "我承诺这不是 null" 若赋值 null → ⚠️ 警告
string? name 可空引用 "这可能是 null" 若直接访问成员 → ⚠️ 警告
name! 空包容操作符Null-forgiving "我断言它不是 null——闭嘴" 抑制该表达式上的所有 NRT 警告
name ?? "default" 空合并Null-coalescing 左边是 null 时取右边 结果被追踪为 not-null
x ??= y 空合并赋值 (C# 8) 若 x 为 null 则赋值 y 短路求值

流分析Flow Analysis —— 编译器如何追踪

编译器追踪变量在每条执行路径上的 null 状态MaybeNull / NotNull

✅ 编译器放行(自动推断 NotNull)

string? name = GetName();

// null 检查后
if (name != null)
    Use(name.Length); // ✅ NotNull

// throw 守卫后
if (name == null)
    throw new ArgumentNullException();
Use(name.Length); // ✅ NotNull

// null-coalescing 后
var s = name ?? "";
Use(s.Length); // ✅ NotNull

⚠️ 编译器警告(无法证明 NotNull)

string? name = GetName();
Use(name.Length); // ⚠️ MaybeNull

// 跨方法调用后状态重置
if (name != null)
{
    DoSomething();     // 编译器不跨方法追踪
    Use(name.Length); // ✅ 仍在 if 块内
}

// 赋值 T? → T
string? a = null;
string b = a; // ⚠️ 警告

Null-state 分析 Attribute

Attribute用途
[NotNullWhen(true)]TryXxx 模式:返回 true 时 out 参数为 NotNull
[NotNullWhen(false)]返回 false 时 out 参数为 NotNull
[MaybeNullWhen(true)]返回 true 时 out 参数可能为 null
[MemberNotNull(nameof(field))]方法调用后指定字段不为 null(如 OnInit
public bool TryGetUser(int id,
    [NotNullWhen(true)] out User? user)
{
    user = _users.FirstOrDefault(u => u.Id == id);
    return user != null;
}
// 调用侧:if (TryGetUser(42, out var u)) → u 自动 NotNull

常见模式

DI 构造注入

// 构造函数中赋值 → 编译器追踪到 → 不警告
public class UserService
{
    private ILogger _logger;
    public UserService(ILogger logger) { _logger = logger; }
}

EF Core 导航属性

public Customer Customer { get; set; } = null!;
// null! = 给初始值 null + ! 让编译器别管(EF 反射填充)
NRT 是编译时检查,不是运行时保证。JIT 不插入 null 检查。通过反射调用、或未开 nullable 的代码仍可传入 null。BCL 公开 API 仍保留显式 ThrowIfNull(.NET 6+)。

迁移策略Migration

  1. warnings 先不报错:<Nullable>warnings</Nullable>
  2. 逐文件加 #nullable enable,修完一个再下一个
  3. 公共 API 优先 → internal 代码 → 测试项目
  4. DI / EF Core / 反射场景善用 null!

相关速查