<Nullable>enable</Nullable>C# 8 引入的可空引用类型(Nullable Reference Types, NRT) 的开关,作用是让引用类型在编译期变得“不可空”,从而在编译阶段发现潜在的 NullReferenceException

下面我从「它是干嘛的 → 怎么生效 → 行为变化 → 常见误区 → 实战建议」一步步讲清楚。


一、一句话结论

<Nullable>enable</Nullable> 打开后,引用类型默认“不能为 null”,编译器会帮你找空引用 bug。

它不是“运行时功能”,而是 编译期静态分析工具


二、没有 <Nullable>enable 时的世界(默认)

string name = null;      // ✅ 编译通过
Console.WriteLine(name.Length); // 💥 运行期炸

引用类型默认就是“可为空”,编译器不管你。


三、开启 <Nullable>enable 后发生了什么?

1️⃣ 在项目文件中启用

<PropertyGroup>
  <TargetFramework>net6.0</TargetFramework>
  <Nullable>enable</Nullable>
</PropertyGroup>

2️⃣ 引用类型的语义变了

写法含义
string不能为 null(非空引用类型)
string?可以为 null(可空引用类型)
string name;        // ❌ CS8618:未初始化非可空属性
string? nickName;  // ✅ OK

3️⃣ 编译器开始“找茬”

❌ 错误示例

string GetName()
{
    return null; // ❌ 返回类型是非可空的
}

✅ 正确写法

string? GetName()
{
    return null;
}

或:

string GetName()
{
    return "Tom";
}

四、最常见的使用场景(ASP.NET Core)

✅ Controller 中的 DTO

public class UserDto
{
    public string UserName { get; set; } = null!; // ✅ 告诉编译器“我知道不会为空”
    public string? Email { get; set; }
}

👉 含义:

  • UserName:前端必须传
  • Email:可选

✅ EF Core 实体

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; } = null!; // 数据库列 NOT NULL
    public string? Description { get; set; }  // 可空列
}

EF Core 会根据可空性推断数据库表结构。


五、编译器会给出哪些警告?(重点)

1️⃣ CS8618:非可空属性未初始化

public class User
{
    public string Name { get; set; } // ⚠ CS8618
}

✅ 解决方式:

public string Name { get; set; } = "";

public string Name { get; set; } = null!;

2️⃣ CS8602:可能的空引用解引用

User? user = null;
var name = user.Name; // ⚠ CS8602

✅ 修复:

if (user is not null)
{
    var name = user.Name;
}

3️⃣ CS8625:将 null 字面量赋给非可空类型

string s = null; // ⚠

六、几个“看起来奇怪但很常用”的写法

✅ 1. = null!(最常用)

public string Title { get; set; } = null!;

含义:

“我保证它会在运行时被赋值,别管我。”

✅ 常见于:

  • EF Core
  • ASP.NET Core Model Binding
  • 反序列化

✅ 2. [AllowNull] / [NotNull]

[AllowNull]
public string Name { get; set; } = "";

表示:

  • 外部可以传 null
  • 但内部永远是非 null

✅ 3. [MaybeNull], [NotNullWhen]

public bool TryGetValue([NotNullWhen(true)] out string? value)
{
    value = null;
    return false;
}

这是 BCL 级别的高级用法,写库时很有用。


七、它不会影响运行时行为(很重要)

<Nullable>enable 不会改变 IL,也不会加 Null 检查

它只是:

  • ✅ 编译期静态分析
  • ✅ 给你警告
  • ❌ 不抛异常
  • ❌ 不改 runtime

八、常见误区

❌ 误区 1:开了就绝对不会有 Null 异常

不是的。

string? s = GetFromDb();
string t = s!; // 💥 仍然可能炸

! 是“我比编译器聪明”的意思。


❌ 误区 2:所有项目都要开

✅ 新项目:强烈建议开
⚠ 老项目:渐进式开启更安全


❌ 误区 3:DTO 全写成 string?

❌ 错。
✅ 应该是:语义驱动

场景是否可空
用户名
邮箱
创建时间

九、渐进式迁移老项目的推荐姿势

<Nullable>annotations</Nullable>

或按文件开启:

#nullable enable
public class UserDto
{
    public string Name { get; set; } = null!;
}
#nullable restore

十、一句话总结

<Nullable>enable</Nullable> 的本质是:把“引用类型能不能为 null”从约定变成类型系统的一部分,让编译器帮你提前消灭 NullReferenceException

标签: none

添加新评论