<Nullable>enable</Nullable>的作用是什么
<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; // ✅ OK3️⃣ 编译器开始“找茬”
❌ 错误示例
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。