为什么在 C# 10 中我会在 init 属性上收到编译器警告 CS8618

问题描述 投票:0回答:3

我有这个代码

#enable nullable
public class SomeClass
{
  public string SomeProperty { get; init; }
}

出现以下编译器警告:

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

我认为将属性声明为 init 的全部目的是确保在创建时使用属性初始值设定项语法设置属性,例如

var s = new SomeClass { SomeProperty = "some value" };

那么为什么我会收到这个编译器警告?我是否完全误解了

init
的用途?

编辑1:

我不是唯一一个被这个绊倒的人。有一个关于几乎同一件事的 Roslyn 问题

还有一个关于所需属性的提案,其中Richiban 询问

init
props 不是强制性的,原因是什么?我可能有 错过了一些东西,但我想它们会很少被使用 没有
req
修饰符。

“可以初始化”和“必须初始化”之间存在差异,

init
关键字无法清楚地传达这一点。由于围绕不可空引用类型和正确代码的所有模糊性,我错误地认为
init
意味着“必须初始化”。

c# properties initialization nullable
3个回答
7
投票

仅初始化属性不要求在创建时使用属性初始值设定项语法设置属性。它允许以这种方式设置它们,而不是要求所有只读属性都由构造函数设置。

所以用你的代码,

var s = new SomeClass();

... 仍然完全有效,并且最终会导致

s.SomeProperty
为空,这与其类型的可为空性相反。


6
投票

使用 init 关键字,属性值只能在构造函数或 initilize 块中更改。

如果你使用默认构造函数

var s = new SomeClass ();
s.SomeProperty = "A value";//Compiler error CS8852  Init-only property or indexer "SomeClass.SomeProperty" can only be assigned in an object initializer, or on "this" or "base" in an instance constructor or an "init" accessor.

属性 SomeProperty 已用 null 初始化,但它是 not nullable 引用类型。编译器正在报告未来可能出现的运行时错误。 警告 CS8618 退出构造函数时,不可为 null 的属性“SomeProperty”必须包含非 null 值。考虑将属性声明为可为空。

如果发生这种情况没有问题,可以通过以下方式删除警告

1。使用 null 宽容运算符

public class SomeClass
{
    public string SomeProperty { get; init; } = null!;// indicating null value is a correct not null value. 
}

当您尝试访问和使用 SomeProperty 值时,您需要检查可空性以避免空引用异常,即使该类型指示它不可为空。这很令人恼火。

if(s.SomeProperty==null) //OK
{
   //DO SOME STUFF
}

var length = s.SomeProperty?.Length ?? 0; //OK

var length2 = s.SomeProperty;//NullReferenceException

2。为属性分配/初始化有效值

public class SomeClass
{
    public string SomeProperty { get; init; } = "My initial value"; 
}

您可以调用默认构造函数而不初始化属性

var s = new SomeClass (); //Some property is "My initial value"

或使用初始化块调用构造函数

var s = new SomeClass()
{
   SomeProperty = "My new intial value"
};


var length = s.SomeProperty.Length; //NO exception / No problem

3.创建带参数的构造函数,避免使用默认构造函数。

错误/警告仅出现在调用者代码中。

public class SomeClass
{
    public string SomeProperty { get; init; } //ALL OK. CS8618 dissapeared / No Warning.

    public SomeClass(string someProperty)
    {
        SomeProperty = someProperty;
    }
}

var s = new SomeClass(); //CS7036 ERROR.


var s2 = new SomeClass() //CS7036 ERROR.
{
    SomeProperty = "My new intial value"
};

var s3 = new SomeClass("My new initial value"); //OK. No Warning

var s4 = new SomeClass("My new initial value") //OK. No Warning.
{
    SomeProperty = "My other new intial value"
};

s4.SomeProperty = "A value"; //CS8852 ERROR. SomeProperty is enabled to only be initialized in constructor or intialize block.

4
投票

正如编辑和其他答案所指出的,这种行为是由于

init
只会使对象/值在初始化后无法更改,如果它是您不允许的属性,这会变得很奇怪为 null,但同时允许父级在没有上述属性的情况下进行初始化。

随着 C# 11 和引入 Required Members,您现在可以使用

required
关键字来指定必须在创建时初始化成员(如果这是您想要实现的目标):

public class SomeClass
{
    public required string SomeProperty { get; set; }
}

或者,如果您既希望在创建实例时设置该属性,又希望它变得不可变,则可以将其与 init 结合使用:

public class SomeClass
{
    public required string SomeProperty { get; init; }
}

© www.soinside.com 2019 - 2024. All rights reserved.