构造函数是在 C# 中初始化类中不可为空的属性的唯一方法吗?

Posted

技术标签:

【中文标题】构造函数是在 C# 中初始化类中不可为空的属性的唯一方法吗?【英文标题】:Is constructor the only way to initialize non-nullable properties in a class in C#? 【发布时间】:2020-04-19 04:58:39 【问题描述】:

我已切换到在使用 C#8 的项目中启用可为空。现在我有以下课程:

public class Request

    public string Type  get; set; 
    public string Username  get; set; 
    public string Key  get; set; 

编译器当然抱怨它不能保证这些属性不会为空。除了添加一个接受不可为空字符串的构造函数之外,我看不到任何其他方法来确保这一点。

这对于一个小类来说似乎很好,但是如果我有 20 个属性,这是在构造函数中将它们全部列出的唯一方法吗?是否有可能以某种方式例如用初始化器强制它:

var request = new Request  Type = "Not null", Username = "Not null" ; // Get an error here that Key is null

附:有一个很好的答案建议对属性使用 iniatlizer,但这并不总是有效,例如对于类型,例如Type 不能只是初始化为某个随机值

【问题讨论】:

初始化器创建一个 empty 对象,然后设置值。它与接受参数的构造函数不同。代码也不是less。你真的要使用对象初始化器吗? @PrasadTelkikar 我不认为你说的是​​真的。我至少收到一个编译器警告,我必须自己初始化它。或者即使是真的,也不会阻止警告 如果这样做,您可以在属性本身中设置默认值。您可以添加属性告诉编译器属性一旦设置就不会为空,例如NotNull @PanagiotisKanavos 不,这只是我想到的一种理论上的可能性,我只是真的想了解新的不可为空类型的正确行动方案。我真的不想在那里接受 null 因为我事先知道它是无效的,但是用很多参数编写构造函数似乎有点太多了 @IlyaChernomordik 选项那里,但你必须决定你真正想要什么。这些“可选”属性是否具有默认值? props 或字段 必须 被初始化,null 现在可能是也可能不是适当的默认值。 【参考方案1】:

对象初始化语法实际上只是显式分配给字段或属性设置器的简写,即

var request = new Request  Type = "Not null", Username = "Not null" ;

相当于:

var request = new Request();   // <-- All properties are default
request.Type = "Not null";     // <-- Type and key are default
request.Username = "Not null"; // <-- Key is still default

如您所见,Request 实例仍会经历几个状态,其中属性处于默认状态,除非您在构造函数中分配不同的默认值,否则对于字符串等引用类型,该状态将为 null根据其他答案。

另外,通过指定不同的默认值

public string Type  get; set;  = ""
public string Username  get; set;  = "";
public string Key  get; set;  = "";

相当于在默认构造函数中分配这些值,即

public Request()

    Type = "";
    UserName = "";
    Key = "";

您可以想象,如果您随后立即使用对象初始化器语法再次更改值,这有点浪费。

作为替代方案,如果您不需要可变类,我建议您提供一个或多个构造函数重载,然后为任何缺少的字段提供合适的非空默认值,然后创建属性不可变例如

public class Request

    public Request(string type = "", string userName = "", string key = "")
    
         Type = type;
         Username = userName;
         Key = key;
    

    public string Type  get;      // <-- No setter = immutable.
    public string Username  get; 
    public string Key  get; 

您现在可以实例化该类,而无需经历任何属性为空的状态,也无需中间默认分配的开销,例如

var myRequest = new Request(key: "SomeKey"); // <-- Username and Type are defaulted to ""

【讨论】:

C#9 有一个新的“init”关键字,但它还没有帮助。这里有一个 github 问题 (github.com/dotnet/roslyn/issues/44889) 可能会解决它,以便我们将来可以使用初始化语法【参考方案2】:

在定义中初始化它们

public string Type  get; set;  = ""
public string Username  get; set;  = "";
public string Key  get; set;  = "";

对象初始化语法“new Request ”只是 new then 赋值的语法糖,所以不要认为在这种情况下你会出错

【讨论】:

只有2种方式吗?构造函数还是直接初始化?附言带有受保护构造函数的类型,例如Type,那么它只是构造函数呢?

以上是关于构造函数是在 C# 中初始化类中不可为空的属性的唯一方法吗?的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 10.0 中启用了可为空的仅初始化引用属性

必须初始化不可为空的实例字段“taskTitle”

声明为不可为空的 Kotlin 属性即使具有初始化值也可以为空

C# 8 中的不可为空的引用类型在运行时可以为空吗?

C# 8.0 不可为空的引用类型和选项模式

c#静态类中的8个可为空的引用类型