为 DTO 使用可空引用类型的最佳实践

Bri*_*per 31 c# non-nullable c#-8.0 nullable-reference-types

我有一个 DTO,它是通过从 DynamoDB 表中读取来填充的。说它目前看起来像这样:

public class Item
{
    public string Id { get; set; } // PK so technically cannot be null
    public string Name { get; set; } // validation to prevent nulls but this doesn't stop database hacks
    public string Description { get; set; } // can be null
}
Run Code Online (Sandbox Code Playgroud)

是否有任何最佳实践来处理这个问题?我宁愿避免使用非无参数构造函数,因为它与 Dynamo SDK(以及其他)中的 ORM 的关系很差。

写起来对我来说似乎很奇怪,public string Id { get; set; } = "";因为这永远不会发生,因为它Id是一个 PK 并且永远不会为空。""即使它以某种方式做了,又有什么用呢?

那么这方面有什么最佳实践吗?

  • 我是否应该将它们全部标记为string?说它们可以为空,即使有些永远不应该。
  • 我是否应该初始化IdName使用,""因为它们永远不应该为空,这显示了即使""永远不会使用的意图。
  • 以上的一些组合

请注意:这是关于 C#8可为空的引用类型如果您不知道它们是什么,最好不要回答。

Pav*_*ski 24

作为一种选择,您可以将default文字与null forgiving operator

public class Item
{
    public string Id { get; set; } = default!;
    public string Name { get; set; } = default!;
    public string Description { get; set; } = default!;
}
Run Code Online (Sandbox Code Playgroud)

由于您的 DTO 是从 DynamoDB 填充的,您可以使用MaybeNull/NotNull 后置条件属性来控制可空性

  • MaybeNull 不可为空的返回值可能为空。
  • NotNull 可为空的返回值永远不会为空。

但是这些属性只影响对使用它们注释的成员的调用者的可空分析。通常,您将这些属性应用于方法返回、属性和索引器 getter。

因此,您可以将所有属性视为不可为空的MaybeNull属性并用属性装饰它们,指示它们返回可能的null

public class Item
{
    public string Id { get; set; } = "";
    [MaybeNull] public string Name { get; set; } = default!;
    [MaybeNull] public string Description { get; set; } = default!;
}

Run Code Online (Sandbox Code Playgroud)

以下示例显示了更新Item类的用法。如您所见,第二行不显示警告,但第三行显示

var item = new Item();
string id = item.Id;
string name = item.Name; //warning CS8600: Converting null literal or possible null value to non-nullable type.
Run Code Online (Sandbox Code Playgroud)

或者您可以将所有属性NoNull设为可空属性并用于指示返回值不能为nullId例如)

public class Item
{
    [NotNull] public string? Id { get; set; }
    public string? Name { get; set; }
    public string? Description { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

警告将与前面的示例相同。

输入参数、属性和索引器设置器也有AllowNull/DisallowNull 前置条件属性,以类似的方式工作。

  • AllowNull 不可为空的输入参数可能为空。
  • DisallowNull 可为空的输入参数永远不应为空。

我认为它不会帮助你,因为你的类是从数据库填充的,但你可以使用它们来控制属性设置器的可空性,就像第一个选项一样

[MaybeNull, AllowNull] public string Description { get; set; }
Run Code Online (Sandbox Code Playgroud)

对于第二个

[NotNull, DisallowNull] public string? Id { get; set; }
Run Code Online (Sandbox Code Playgroud)

在这篇开发博客文章中可以找到一些有用的细节和后/前提条件的例子


Jer*_*ney 12

更新:从 2021 年 3 月 9 日发布的 .NET 5.0.4(SDK 5.0.201)开始,以下方法现在将产生CS8616警告。鉴于此,如@Pavel-Anikhouski's answer顶部所讨论的那样,default使用空值原谅运算符(即default!)设置值的方法现在是您最好的选择。


在这种情况下,教科书的答案是string?为您的Id属性使用 a ,但[NotNull]属性装饰它:

public class Item
{
  [NotNull] public string? Id { get; set; }
  public string Name { get; set; }
  public string? Description { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

参考:根据文档,该[NotNull]属性“指定即使相应类型允许输出也不为空”。

那么,这里到底发生了什么?

  1. 首先,string?返回类型可防止编译器警告您该属性在构造期间未初始化,因此将默认null
  2. 然后,[NotNull]当将属性分配给不可为空的变量或尝试取消引用它时,该属性会防止出现警告,因为您正在通知编译器的静态流分析,实际上,该属性永远不会是null

警告:与涉及 C# 的可空性上下文的所有情况一样,从技术上讲,没有什么可以阻止您仍然null在此处返回值,因此可能会引入一些下游异常;即,没有开箱即用的运行时验证。C# 提供的所有内容都是编译器警告。当您介绍时,[NotNull]您可以通过提供有关您的业务逻辑的提示来有效地覆盖该警告。因此,当您使用 注释属性时[NotNull],您对自己的承诺“这永远不会发生,因为它Id是一个 PK 并且永远不会为空”负责。

为了帮助您保持该承诺,您可能希望使用以下属性注释该[DisallowNull]属性:

public class Item
{
  [NotNull, DisallowNull] public string? Id { get; set; }
  public string Name { get; set; }
  public string? Description { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

参考:根据文档,该[DisallowNull]属性“指定null即使相应类型允许也不允许作为输入。”

这可能与您的情况无关,因为值是通过数据库分配的,但是[DisallowNull]如果您尝试将null(能够)值分配给,该属性会向您发出警告Id即使返回类型允许它是空。在这方面,Id就C# 的静态流分析而言,它的行为与 a完全一样string,同时允许值在对象的构造和属性的填充之间保持未初始化。

注意:正如其他人所提到的,您也可以通过分配Id默认值default!或来获得几乎相同的结果null!诚然,这在某种程度上是一种风格偏好。我更喜欢使用可空性注释,因为它们更明确并提供精细控制,而很容易滥用它!作为关闭编译器的一种方式。如果我知道我永远不会使用该值(即使它是默认值),则使用值显式初始化属性也会使我烦恼。