如何在 C# 项目中启用可空引用类型
可空引用类型不是默认开启的,必须显式启用,否则
?语法会被编译器忽略(仅作注释用途)。启用后,编译器会对变量、参数、返回值等进行空状态分析,并在潜在空引用访问处发出警告(如
CS8602、
CS8600)。
启用方式有两种,推荐在项目文件中全局开启:
在.csproj文件的
<propertygroup></propertygroup>中添加:
<Nullable>enable</Nullable>或使用
#nullable enable预处理器指令在单个文件顶部启用(不推荐长期使用,难以统一管控) 注意:启用后,所有引用类型变量默认为“不可为空”,例如
string name表示
name不应为
null;要允许为空,必须显式声明为
string? name
为什么 string?
不等于 Nullable<string></string>
string?是可空引用类型的语法糖,和
Nullable<t></t>(即
T?用于值类型)**完全不同机制**。后者是运行时类型(如
int?编译为
Nullable<int></int>),而前者只是编译期注解,不改变 IL 或运行时行为。
常见误解:
typeof(string?)就是
typeof(string),不是新类型
default(string?)仍是
null,不会变成某种“可空包装对象”
string?的作用纯粹是触发编译器空状态分析——它告诉编译器:“这个变量可以合法地持有
null,别对它报
CS8602”
处理常见警告:CS8602(解引用可能为 null 的表达式)
这是启用可空引用类型后最常遇到的警告,通常出现在调用方法、访问属性或索引前未确认非空。编译器无法静态证明安全时就会报此警告。
解决方式取决于上下文,优先级从高到低:
用!运算符断言非空(仅当你**完全确定**该值不为
null):
name!.Length—— 注意:若运行时为
null,仍抛
NullReferenceException用空合并运算符提供默认值:
name ?? "unknown"用条件访问(
?.)避免解引用:
person?.Name?.ToUpper()提前检查并分支处理:
if (name is not null) { ... } 或 if (!string.IsNullOrEmpty(name)) { ... }
不要滥用
!,尤其在外部输入(如 API 参数、配置读取、数据库查询结果)场景下,应优先用显式判空或提供默认值。
与泛型、集合、异步方法配合时的典型坑
可空性会穿透泛型和常见类型,但规则容易被忽略:
List<string></string>表示“非空列表,且每个元素都非空”;
List<string></string>才表示“列表本身非空,但元素可为空”
T?在泛型中含义取决于
T是否为引用类型:
void Process<T>(T? value) where T : class等价于
T value(因为
T已限定为引用类型,
T?即显式允许
null)
Task<string></string>表示“任务完成后返回非空字符串”;若可能返回
null,应声明为
Task<string></string>EF Core 查询中,
FirstOrDefaultAsync()返回
T?(
T是引用类型时),但编译器不一定能推断出其可空性,必要时需手动补
??或
!
最易被忽略的一点:可空引用类型分析依赖完整的调用链。如果某个方法没标注返回值是否可空(比如第三方库未启用
Nullable),编译器会将其视为“未知空状态”,可能导致下游误报或漏报。此时需结合
[AllowNull]、
[MaybeNull]等可空性属性辅助标注。
