为什么 C# 编译器无法确认构造函数结束前字段已非 null?
为什么已经赋值,仍然出现 CS8618?
这段代码的构造函数正常执行完后,file_path 确实不为 null。出现 CS8618,是因为编译器无法仅凭对 InitializeDocument() 的调用,确认该字段一定会被初始化。
为了判断构造函数结束时的字段状态,编译器不会递归展开任意被调用方法的实现。可以直接在初始化位置赋值,也可以用 MemberNotNull 声明辅助方法的初始化效果。
CS8618 默认是一条编译警告。如果项目将警告视为错误,它会导致构建失败。这是静态分析的结果,并不表示编译器发现了这次运行中的实际空值。
方法一:在构造函数中直接赋值
如果初始化只涉及这一条赋值,放进构造函数即可:
public class Document
{
private string file_path;
public Document()
{
file_path = @"C:\mydocument.doc";
}
}
这样,编译器可以直接确认,构造函数正常结束前 file_path 已被赋予非 null 值。
如果所有实例都使用相同的初始路径,也可以在字段声明处初始化:
private string file_path = @"C:\mydocument.doc";
方法二:保留辅助方法,添加 MemberNotNull
如果初始化逻辑需要集中在 InitializeDocument() 中,可以使用 System.Diagnostics.CodeAnalysis 中的 MemberNotNull:
using System.Diagnostics.CodeAnalysis;
public class Document
{
private string file_path;
public Document()
{
InitializeDocument();
}
[MemberNotNull(nameof(file_path))]
private void InitializeDocument()
{
file_path = @"C:\mydocument.doc";
}
}
[MemberNotNull(nameof(file_path))] 声明该方法正常返回时,当前实例的 file_path 字段已非 null。编译器据此更新调用后的字段状态,也会检查方法实现是否满足这一声明。
这个特性不会自动赋值,也不会插入运行时空值检查。方法仍须在每条正常返回路径上完成初始化;抛出异常的路径不要求成功初始化,因为方法没有正常返回。
使用这一方案的前提是编译器支持该特性,且项目引用中包含 MemberNotNullAttribute 类型。
是否应该改成 string?、required 或 null!?
这几种写法含义不同,不宜只为消除警告而替换:
string?:表示字段允许为null,适合业务上确实存在空值的情况。required:主要要求调用方在创建对象时初始化成员。当前字段由类内部初始化,没有必要因此改成调用方负责的成员。null!:抑制可空性警告,不会为字段提供非 null 值,也不能证明初始化方法执行正确。
对于给出的代码,直接在构造函数或字段声明处赋值最简单。如果需要保留辅助方法,可以用 MemberNotNull 表达初始化保证。
备注:内容仅供参考。
