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

M.H*_*san 25 c# orm dto c#-8.0 nullable-reference-types

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

public class Connection
    {
        public string ServiceUrl { get; set; }
        public string? UserName { get; set; }
        public string? Password { get; set; }
        //... others 
    }
Run Code Online (Sandbox Code Playgroud)

但我收到错误:

CS8618:不可为空的属性“ServiceUrl”未初始化。考虑将该属性声明为可为空的。

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

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

var connection = new Connection
{
  ServiceUrl=some_value,
  //...
}
Run Code Online (Sandbox Code Playgroud)

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

M.H*_*san 33

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

  1. EF Core建议使用null-forgiving 运算符初始化null!

    public string ServiceUrl { get; set; } = null! ;
    //or
    public string ServiceUrl { get; set; } = default! ;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用支持字段:

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

  • 这非常烦人,我们必须在启用可为空的情况下手动执行此操作。额外的工作并没有多大好处。 (5认同)
  • 我希望我能写字符串!而不是字符串 .. { get; 放; } = null!,这变得如此重复......或者只是标记该类,其所有属性都不应为空...... (4认同)
  • 我强烈不同意 EF Core 文档的建议,因为它将对象置于无效状态 - 使用“!”运算符只是将问题隐藏起来,因为希望没有人尝试使用从未初始化的“字符串” ` 对象中的属性将导致 NRE。更好的解决方案是 EF Core 支持主构造函数,并且更好的是:如果 C# 具有更好的构造函数人体工程学。 (4认同)

Ath*_*ras 9

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

字符串的默认值为空,因此您将

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

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

  2. 或者在默认构造函数中初始化值,这样你就可以摆脱警告

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

  4. 正如 robbpristley 提到的,使它可以为空。

  • 当然,或者添加一个接受服务 URL 作为不可为空字符串的构造函数。 (6认同)

Jér*_*VEL 9

我已经使用新的Nullable Reference TypesNRT)功能一段时间了,我必须承认我最大的抱怨是编译器在类的声明中向您发出这些警告。

\n

在我的工作中,我构建了一个微服务,试图解决所有这些警告,从而导致相当复杂的代码,尤其是在处理 EF Core、AutoMapper 和 DTO 时,这些警告作为 Nuget 包供 .NET Core 用户共享。\n这个非常简单的微服务仅仅因为 NRT 功能导致我采用疯狂的非流行编码风格,服务很快就变得一团糟。

\n

然后,在阅读了 Cezary Pi\xc4\x85tek\ 的文章Improving non-nullable reference types Handling后,我发现了很棒的SmartAnalyzers.CSharpExtensions.Annotations Nuget 包。

\n

这个 Nuget 包将不可为 null 的责任转移给实例化对象的调用者代码,而不是类声明代码。

\n

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

\n
[assembly: InitRequiredForNotNull]\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
<ItemGroup>\n    <AssemblyAttribute Include="SmartAnalyzers.CSharpExtensions.Annotations.InitRequiredForNotNullAttribute" />\n</ItemGroup>\n
Run Code Online (Sandbox Code Playgroud)\n

CSE001 Missing initialization for properties我还通过在我的.editorconfig文件中设置它来将默认错误更改为警告

\n
[*.cs]\ndotnet_diagnostic.CSE001.severity = warning\n
Run Code Online (Sandbox Code Playgroud)\n

您现在可以Connection像平常一样使用您的类,而不会出现任何错误

\n
[*.cs]\ndotnet_diagnostic.CSE001.severity = warning\n
Run Code Online (Sandbox Code Playgroud)\n

只需要注意一件事。让我们这样考虑你的班级

\n
var connection = new Connection()\n{\n    ServiceUrl = "ServiceUrl"\n};\n
Run Code Online (Sandbox Code Playgroud)\n

在这种情况下,当你实例化你的对象时,就像

\n
public class Connection\n{\n    public string ServiceUrl { get; }\n    public string? UserName { get; }\n    public string? Password { get; }\n\n    public Connection(string serviceUrl, string? userName = null, string? password = null)\n    {\n        if (string.IsNullOrEmpty(serviceUrl))\n            throw new ArgumentNullException(nameof(serviceUrl));\n\n        ServiceUrl = serviceUrl;\n        UserName = userName;\n        Password = password;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

NugetSmartAnalyzers.CSharpExtensions.Annotations包不会分析您的构造函数来检查您是否确实正在初始化所有不可为空的引用类型。它只是信任它并相信您在构造函数中正确执行了操作。因此,即使您忘记了这样的不可空成员,它也不会引发任何错误

\n
var connection = new Connection("serviceUrl");\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n


Dre*_*per 6

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

[{**/Responses/*.cs,**/Requests/*.cs}]
# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable.
dotnet_diagnostic.CS8618.severity = none
Run Code Online (Sandbox Code Playgroud)


ajb*_*ven 6

如果您使用 C# 11 或更高版本,则required 成员会为此提供解决方案:

public required string ServiceUrl { get; set; }
Run Code Online (Sandbox Code Playgroud)

如果您尚未升级到 C# 11,请改用 null-forgiving 运算符:

public string ServiceUrl { get; set; } = null!;
Run Code Online (Sandbox Code Playgroud)