C#10 可空模式:如何告诉编译器我在构造函数中间接设置了不可空属性?

Har*_*rry 11 c# nullable c#-10.0

考虑一个例子:

class Test {

    string S { get; set; }

    public Test() {
        Init();
    }

    private void Init() {
        S = "hello";
    }
 
}
Run Code Online (Sandbox Code Playgroud)

使用可空 C# 项目功能,此示例将触发编译器警告:

警告 CS8618 退出构造函数时,不可为 null 的属性“S”必须包含非 null 值。考虑将该属性声明为可为空。

但是,退出构造函数时,该属性确实包含非空值,只是不是直接在构造函数中设置,而是在从构造函数无条件调用的方法中间接设置。

这个例子清楚地表明该属性不可能为S空。Test创建类的实例时,Init()将无条件调用该方法,因此该S属性始终设置为“hello”。

当然,这个警告可以在代码中被抑制,但这看起来很丑陋。这是告诉编译器我确实S在其他地方将该属性设置为非空值的更好方法吗?

顺便说一句,如果您真的想知道为什么要在构造函数中间接设置值,让我们考虑一下类型D的另一个派生属性Derived。要创建字符串的实例,Derived必须首先解析字符串,我们不想每次读取属性时都解析字符串D

因此,更实际的代码看起来更像是这样的:

class Test {

    public string S { 
        get => _S;
        set => D = new Derived(_S = value);
    }

    public Derived D { get; private set; }

    public Test(string s) => D = new Derived(_S = s);

    private string _S;
 
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,退出构造函数时,S和都D被设置为非空值。但该代码仍然会触发编译器警告 CS8618。

Luk*_* Vo 19

用于MemberNotNullAttribute标记你的函数:

using System.Diagnostics.CodeAnalysis;

class Test
{

    string S { get; set; }

    public Test()
    {
        Init();
    }

    [MemberNotNull(nameof(S))]
    private void Init()
    {
        S = "hello";
    }

}
Run Code Online (Sandbox Code Playgroud)

如果您没有在 中初始化 S,编译器现在会抱怨Init

在此输入图像描述

更多场景请参阅本文:空状态静态分析的属性