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

Posted

技术标签:

【中文标题】C#10 可空模式:如何告诉编译器我在构造函数中间接设置了不可空属性?【英文标题】:C#10 nullable pattern: how to tell the compiler I set the non-nullable property in the constructor indirectly? 【发布时间】:2021-11-22 14:41:24 【问题描述】:

考虑一个例子:

class Test 

    string S  get; set; 

    public Test() 
        Init();
    

    private void Init() 
        S = "hello";
    
 

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

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

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

这个例子清楚地表明S 属性不可能永远为空。当Test 类的实例被创建时,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。

【问题讨论】:

好问题,答案是您的第一个示例的正确解决方案。但是,您的“更现实”代码不会产生任何错误 - _S is 在构造函数中设置。 S 本身不需要设置,因为它的 getter 返回不可为空的东西。 至于你的其他问题,我不补充我的答案,因为我不确定我是否正确,如果我错了,请有人纠正。编译器并不那么聪明,无法知道您使用另一种方法的意图(这就是您必须标记该方法的原因)来设置 S 值。从技术上讲,它可以更深入地调用,但它只是不值得 IMO。此外,如果您的 Init 方法是 virtual 并且派生类型根本不初始化 S 怎么办?太多的场景 IMO 和用属性装饰来告诉编译器“我知道我在做什么”更好。 @LukeVo:当方法被标记为虚拟时 - 这将是触发编译器警告的充分理由。当方法不是virtual时,不能被覆盖,所以必须调用初始化S的方法。否则试图覆盖Init 会触发编译器错误。在方法隐藏的情况下 - 无论如何都会调用原始的 Init 。但是你让我很好奇 - 是否有可能使用 MemberNotNullAttribute 以某种方式破坏代码?正如您所提到的,我猜该属性的存在是出于 CA 性能的原因。 @Harry 我尝试将其更改为protected virtual void Init() 并用派生类型覆盖它。如果您不致电base.Init(),您会收到相同的警告。关于编译器非虚拟Init,性能将是 IMO 的一个原因,另一个是,Init 可能会调用更多方法InitS 调用InternalInitS 下线,这是不值得的。 【参考方案1】:

使用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

【讨论】:

这正是我想要的。他们考虑了一切。我想如果它是自动的,它可能会使 CA 变得相当慢,甚至可能容易出错。甚至可能是:“如果你做了这样的间接,请在代码中清楚明确地注明”的理由,这也很合理。

以上是关于C#10 可空模式:如何告诉编译器我在构造函数中间接设置了不可空属性?的主要内容,如果未能解决你的问题,请参考以下文章

带有数组的 C++ 构造函数初始化列表

如何解决此链接错误

如何将十进制变量转换为可空类型

C ++没有适当的默认构造函数我迷路了

注册一个没有默认构造函数的类型

c++11 类中关于defaultexplictimplicitnoexceptfinal的详解