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

C#10 nullable pattern: how to tell the compiler I set the non-nullable property in the constructor indirectly?

考虑一个例子:

class Test {

    string S { get; set; }

    public Test() {
        Init();
    }

    private void Init() {
        S = "hello";
    }
 
}

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

Warning CS8618 Non-nullable property 'S' must contain a non-null value when exiting the constructor. Consider declaring the property as nullable.

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

这个例子清楚地表明 S 属性 不可能为空。当 Test class 的实例被创建时, Init() 方法被无条件调用,所以 S 属性 总是被设置为“hello”。

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

顺便说一句,如果您真的想知道为什么要在构造函数中间接设置值,让我们考虑一下 Derived 类型的另一种派生 属性 D。要创建 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;
 
}

如您所见,SD 在退出构造函数时都设置为非空值。 然而代码仍然触发编译器警告CS8618。

使用MemberNotNullAttribute标记你的功能:

using System.Diagnostics.CodeAnalysis;

class Test
{

    string S { get; set; }

    public Test()
    {
        Init();
    }

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

}

编译器现在会抱怨 如果 你没有在 Init:

中初始化 S

查看更多场景in this article: Attributes for null-state static analysis