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

Posted

技术标签:

【中文标题】使用参数验证初始化可变不可空属性时避免 CS8618 警告【英文标题】:Avoid CS8618 warning when initializing mutable non nullable property with argument validation 【发布时间】:2020-06-27 06:47:42 【问题描述】:

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

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

public class Person


    public string Name  get; set; 

    public Person(string name)
             
        Name = name;
    

到目前为止没有问题。但是考虑到现实世界的场景,我经常想检查属性的有效性,因为它是一个公共的可变属性,我必须确保模型在属性发生变化时保持不变。

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警告,基本是说:

不可为空的字段_name 未初始化。考虑声明 字段为可空类型。

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

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

#pragma warning restore CS8618

但我认为总是这样做既多余又乏味。我是否滥用了某些东西,或者有没有更好的方法在没有警告的情况下编写此类属性?

当然,我可以按照编译器的建议将属性类型更改为string?,但从概念上讲,它作为一种解决方案是不可接受的,因为 Person 应该始终具有非 null 名称,并且我们希望在域类中明确说明这种不变条件。

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

【问题讨论】:

并不是说它有助于立即修复。 C# 9.0 的Init only properties 似乎是完美的选择。同样,不是答案,而是值得期待的东西! 【参考方案1】:

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

private string _name = default!;

private string _name = null!;

还有一个开放的GitHub issue。

您还可以将_name 声明为string? 并指定Name 属性的返回值不能为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);

在这种情况下你会得到

警告 CS8625:无法将 null 文字转换为不可为 null 的引用 输入。

ArgumentNullException 之前将被抛出。

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

【讨论】:

谢谢,当您确定将在流程中以另一种方法设置该值时,使用默认值的容错运算符是一个干净的解决方案 IMO 谢谢!尤其是考虑到 VS 中的链接指向的页面显示:对不起,我们没有关于此 C# 错误的详细信息 string _name = ""; 初始化一个字段或属性怎么样?有什么区别吗?考虑到我不在乎它是 null 并对待 null 并清空相同。【参考方案2】:

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

[*.cs]

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

【讨论】:

【参考方案3】:

基于this:

初始化字段的警告 Q:为什么构造函数间接初始化或者在构造函数外初始化的字段会报警告?

A: 编译器识别当前明确分配的字段 仅构造函数,并警告声明为不可为空的其他字段。 这忽略了可能初始化字段的其他方式,例如工厂 方法、辅助方法、属性设置器和对象初始化器。我们 将研究识别常见的初始化模式以避免 不必要的警告。

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

【讨论】:

以上是关于使用参数验证初始化可变不可空属性时避免 CS8618 警告的主要内容,如果未能解决你的问题,请参考以下文章

软件构造课程提纲

使用空基类的聚合初始化时如何避免

Python不可变对象元组(tuple)详解

当初始化列表可用时,为啥现在使用可变参数?

C# 代码契约 - 避免检查空引用的参数

当参数为空列表时,如何避免值限制错误?