使用参数验证初始化可变不可空 属性 时避免 CS8618 警告

Avoid CS8618 warning when initializing mutable non nullable property with argument validation

我有一个关于 nullable reference type system 自 C# 8 起可用的问题。

假设我们有一个 C# 域模型 class 具有可变引用类型 属性,如下所示:

public class Person
{

    public string Name { get; set; }

    public Person(string name)
    {         
        Name = name;
    }
}

到目前为止没问题。但是考虑到现实世界的场景,我经常想检查 属性 的有效性,因为它是一个 public 可变的 属性 并且我必须确保只要 属性 是改变了。

public class Person
{
    private string _name;
    public string Name
    {
        get => _name;
        set => _name =
            value ?? throw new ArgumentNullException("Name is required.");
    }
    public Person(string name)
    {         
        Name = name;
    }
}

然后编译器生成CS8618警告,基本上是说:

Non nullable field _name is not initialized. Consider declare the field as nullable type.

所以每次遇到警告时,我都必须使用以下 pragma 指令将构造函数括起来。

#pragma warning disable CS8618
public Person(string name)
{         
    Name = name;
}
#pragma warning restore CS8618

但我认为总是这样做是多余和乏味的。我是在滥用某些东西还是有更好的方法在没有警告的情况下编写这样的 属性?

当然,我可以按照编译器的建议将 属性 类型更改为 string?,但理论上它作为解决方案是不可接受的,因为 Person 应该始终具有非空名称,我们希望明确说明此类不变性域 class.

中的条件

我考虑的另一个解决方案是放弃参数验证逻辑并仅依赖可为空的编译器警告,但这并不总是可能的(我的意思是通常还需要除空检查之外的验证。),它只是警告常规项目设置,所以我认为这不是一个好的解决方案。

基于this

Warnings for initialized fields Q: Why are warnings reported for fields that are initialized indirectly by the constructor, or outside the constructor?

A: The compiler recognizes fields assigned explicitly in the current constructor only, and warns for other fields declared as non-nullable. That ignores other ways fields may be initialized such as factory methods, helper methods, property setters, and object initializers. We will investigate recognizing common initialization patterns to avoid unnecessary warnings.

话虽如此,目前,将赋值直接移动到构造函数中是唯一可能的方法。可以肯定的是,对于这个 IMO,使用 pragma 指令似乎没问题。

现在您可以通过使用 null-forgiving operator ! 的默认值初始化 _name 字段来避免此警告,例如

private string _name = default!;

private string _name = null!;

还有一个开放的GitHub issue

您还可以将 _name 声明为 string? 并指定 Name 属性 的 return 值不能是 null (即使 string? 类型允许),使用 NotNull 属性

private string? _name;

[NotNull]
public string? Name
{
    get => _name;
    set => _name = value ?? throw new ArgumentNullException("Name is required.");
}

应该没问题,否则编译器会在验证逻辑发生在 setter

之前向您显示警告
set => _name = value ?? throw new ArgumentNullException("Name is required.");

考虑以下代码

var person = new Person(null);

在这种情况下你会得到

warning CS8625: Cannot convert null literal to non-nullable reference type.

之前ArgumentNullException会被抛出。

如果您设置 <TreatWarningsAsErrors>true</TreatWarningsAsErrors> 或将 CS8625 警告视为错误,您的异常将不会被抛出

您可以通过在项目的根目录中创建一个 .editorconfig 文件(带有附加代码)来禁用该规则。它没有解决它,但它不会再显示警告

[*.cs]

# CS8618: Non nullable field _name is not initialized. Consider declare the field as nullable type
dotnet_diagnostic.CS8618.severity = none

您现在可以在 setter 上应用 MemberNotNull 属性,让 C# 编译器知道该方法正在维护 _name 字段的 non-nullability 条件。

C# language reference

using System.Diagnostics.CodeAnalysis;

public class Person
{
    private string _name;
    
    public string Name
    {
        get => _name;

        [MemberNotNull(nameof(_name))]
        set => _name = value ?? throw new ArgumentNullException("Name is required.");
    }

    public Person(string name)
    {         
        Name = name;
    }
}