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;
如您所见,S
和 D
在退出构造函数时都设置为非空值。
但是代码仍然会触发编译器警告 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 可空模式:如何告诉编译器我在构造函数中间接设置了不可空属性?的主要内容,如果未能解决你的问题,请参考以下文章