将 DTO 类 与 ORM 一起使用时 C#8 中的可空引用类型

Nullable reference type in C#8 when using DTO classes with an ORM

我在具有数据传输对象 (DTO) classes 的项目中激活了此功能,如下所示:

public class Connection
    {
        public string ServiceUrl { get; set; }
        public string? UserName { get; set; }
        public string? Password { get; set; }
        //... others 
    }

但是我得到错误:

CS8618: Non-nullable property 'ServiceUrl' is uninitialized. Consider declaring the property as nullable.

这是一个 DTO class,所以我没有初始化属性。这将是初始化 class 以确保属性非空的代码的责任。

例如,调用者可以这样做:

var connection = new Connection
{
  ServiceUrl=some_value,
  //...
}

我的问题:当启用 C#8 的可空性上下文时,如何处理 DTO classes 中的此类错误?

如果它是不可空的,那么编译器在初始化对象时可以做什么?

字符串的默认值为空,所以你会

  1. 要么需要在声明中指定一个字符串默认值

    public string ServiceUrl { get; set; } = String.Empty;

  2. 或者在默认构造函数中初始化值这样就可以去掉警告

  3. 使用 ! 运算符(您不能使用)

  4. 如 robbpriestley 所述使其可为空。

您可以执行以下任一操作:

  1. EF Core 建议 initializing to null! with null-forgiving operator

    public string ServiceUrl { get; set; } = null! ;
    //or
    public string ServiceUrl { get; set; } = default! ;
    
  2. 使用支持字段:

    private string _ServiceUrl;
    public string ServiceUrl
    {
        set => _ServiceUrl = value;
        get => _ServiceUrl
               ?? throw new InvalidOperationException("Uninitialized property: " + nameof(ServiceUrl));
    }
    

在某些情况下可能会派上用场的另一件事:

[SuppressMessage("Compiler", "CS8618")]

可以在成员或整个类型之上使用。


还要考虑的另一件事是在文件顶部添加 #nullable disable 以禁用整个文件的可空引用。

Nullable 引用类型的好处是可以有效地管理空引用异常。所以最好使用指令#nullable enable。它帮助开发人员在 运行 时间避免空引用异常。

如何帮助避免空引用异常: 编译器将在静态流分析期间确保以下两件事。

  1. 该变量已明确分配给非空值。
  2. 变量或表达式之前已经过空值检查 取消引用它。

如果不满足以上条件,编译器会抛出警告。 注意:所有这些活动都是在编译期间发出的。

空引用类型有什么特别之处:

在 C# 8.0 之后,引用类型被认为是不可空的。这样,如果引用类型变量未正确处理空值,编译器会及时发出警告。

如果我们知道引用变量是 NULLABLE,我们可以做什么:

  1. 附加'?'在变量(示例)字符串之前?姓名
  2. 使用 "null forgiving operator"(示例)字符串名称 {get;set;} = 无效的!;或字符串名称 {get;set;} = default!;
  3. 使用"backing fields" (示例):

    私有字符串_name; public 字符串名称 {get { return _name; } 设置 {_name = value;} }

如果我们知道引用变量是不可空的,我们可以做什么:

如果它不可为空,则使用构造函数初始化 属性。

不可空引用类型的好处:

  1. 更好地处理空引用异常
  2. 借助编译时警告,开发人员可以及时更正他们的代码。
  3. 开发人员在设计 class 时会将意图传达给编译器。 此后,编译器将通过代码执行意图。

通常 DTO 类 存储在单独的文件夹中,因此我只是根据路径模式在 .editorconfig 文件中禁用此诊断:

[{**/Responses/*.cs,**/Requests/*.cs}]
# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable.
dotnet_diagnostic.CS8618.severity = none

我已经使用新的 Nullable Reference Types (NRT) 功能一段时间了,我必须承认我最大的抱怨是编译器是在 classes 的声明中给你这些警告。

在我的工作中,我构建了一个 micro-service 试图解决所有这些导致相当复杂的代码的警告,尤其是在处理作为 .NET Core 消费者的 Nuget 包共享的 EF Core、AutoMapper 和 DTO 时. 这个非常简单的 micro-service 很快就变得一团糟,只是因为 NRT 功能导致我疯狂 non-popular 编码风格。

然后我发现了很棒的SmartAnalyzers.CSharpExtensions.Annotations Nuget package after reading Cezary Piątek's article Improving non-nullable reference types handling

这个 Nuget 包正在将 non-nullbable 责任转移到实例化对象的调用者代码,而不是 class 声明代码。

在他的文章中,他说我们可以通过在您的 .cs 文件之一中写入以下行来在整个程序集中激活此功能

[assembly: InitRequiredForNotNull]

例如,您可以将它放在 Program.cs 文件中,但我个人更喜欢直接在我的 .csproj 中激活它

<ItemGroup>
    <AssemblyAttribute Include="SmartAnalyzers.CSharpExtensions.Annotations.InitRequiredForNotNullAttribute" />
</ItemGroup>

我还通过在我的 .editorconfig 文件

中设置将默认的 CSE001 Missing initialization for properties 错误更改为警告
[*.cs]
dotnet_diagnostic.CSE001.severity = warning

您现在可以像往常一样使用 Connection class 而不会出现任何错误

var connection = new Connection()
{
    ServiceUrl = "ServiceUrl"
};

只有一件事需要注意。让我们考虑一下您的 class 这样的

public class Connection
{
    public string ServiceUrl { get; }
    public string? UserName { get; }
    public string? Password { get; }

    public Connection(string serviceUrl, string? userName = null, string? password = null)
    {
        if (string.IsNullOrEmpty(serviceUrl))
            throw new ArgumentNullException(nameof(serviceUrl));

        ServiceUrl = serviceUrl;
        UserName = userName;
        Password = password;
    }
}

在那种情况下,当您实例化对象时

var connection = new Connection("serviceUrl");

SmartAnalyzers.CSharpExtensions.Annotations Nuget 包不会分析您的构造函数来检查您是否真的在初始化所有 non-nullable 引用类型。它只是简单地信任它并相信你在构造函数中做的事情是正确的。因此,即使您忘记了像这样的 non-nullable 成员,它也不会引发任何错误

public Connection(string serviceUrl, string? userName = null, string? password = null)
{
    if (string.IsNullOrEmpty(serviceUrl))
        throw new ArgumentNullException(serviceUrl);

    UserName = userName;
    Password = password;
}

我希望你会喜欢这个 Nuget 包背后的想法,它已经成为我在所有新的 .NET Core 项目中安装的默认包。

要消除 DTO 上的警告,请在 DTO 的 cs 文件的开头指定: #pragma 警告禁用 CS8618

最后: #pragma 警告恢复 CS8618