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


20

我有一个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
}

有什么最佳实践可以解决这个问题?我宁愿避免使用非参数构造函数,因为它与Dynamo SDK(以及其他)中的ORM配合不好。

我写public string Id { get; set; } = "";这本书似乎很奇怪,因为这永远不会发生,因为Id它是PK,而且永远不会为null。""即使以某种方式做了,会有什么用?

那么对此有什么最佳实践吗?

  • 我是否应该将它们全部标记为string?说它们可以为null,即使有些绝对不能为null。
  • 我应该初始化Id,并Name"",因为他们应该永远是零,这表明意图,即使""将永远不会被使用。
  • 以上的一些组合

请注意:这是关于C#8 可为空的引用类型的信息。如果您不知道它们的最佳答案,那就不要回答。


它有点脏,但是您可以#pragma warning disable CS8618在文件顶部打一巴掌。

7
而不是= "",您可以= null!用来初始化一个您永远不会有效的属性null(当编译器无法知道该属性时)。如果Description可以合法的话null,应该宣布为string?。另外,如果DTO的可空性检查比帮助更麻烦,则可以仅将类型包装在#nullable disable/中#nullable restore以仅为此类型关闭NRT。
Jeroen Mostert

@JeroenMostert您应该把它当作答案。
Magnus

3
@Magnus:我不愿意回答任何要求“最佳实践”的问题;这些事情是广泛而主观的。我希望OP可以使用我的评论来发展自己的“最佳实践”。
Jeroen Mostert

1
@IvanGarcíaTopete:虽然我同意使用字符串作为主键是不寻常的,甚至根据情况可能不建议使用,但OP的数据类型选择与该问题无关。这可以很容易地应用于不是主键的必需的,不可为空的字符串属性,甚至不是复合主键一部分的字符串字段,问题仍然存在。
杰里米·卡尼

Answers:


12

作为选择,您可以将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!;
}

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

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

但是,这些属性仅影响对带有注释的成员的调用者的可空分析。通常,您将这些属性应用于方法返回,属性和索引器获取器。

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

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

以下示例显示了更新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.

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

public class Item
{
    [NotNull] public string? Id { get; set; }
    public string? Name { get; set; }
    public string? Description { get; set; }
}

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

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

  • AllowNull 不可为空的输入参数可以为null。
  • DisallowNull 可为空的输入参数绝不能为空。

我认为这对您没有帮助,因为您的类是从数据库填充的,但是您可以使用它们来控制属性设置器的可空性,例如第一个选项

[MaybeNull, AllowNull] public string Description { get; set; }

第二个

[NotNull, DisallowNull] public string? Id { get; set; }

一些有用的细节和后置条件的示例可以在此devblog文章中找到。


6

在这种情况下,教科书的答案是string?对您的Id媒体资源使用,但还要使用[NotNull]属性来装饰它:

public class Item
{
  [NotNull] public string? Id { get; set; }
  public string Name { get; set; }
  public string? Description { get; set; }
}

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

那么,这到底是怎么回事?

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

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

为了帮助您维持这一承诺,您可能希望使用属性为[DisallowNull]属性添加注释:

public class Item
{
  [NotNull, DisallowNull] public string? Id { get; set; }
  public string Name { get; set; }
  public string? Description { get; set; }
}

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

由于您是通过数据库分配值的,因此这可能与您无关,但是[DisallowNull]如果您尝试向分配一个null(able)值Id,则该属性会向您发出警告,即使返回类型允许该值是null。在这方面,Id将采取行动正是string至于C#的静态流分析方面,同时允许值保持在对象的构造和财产的人口之间的初始化。

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


-2

字符串是一种引用类型,始终为空,您无需执行任何特殊操作。仅当您想将此对象类型映射到另一个对象时,才可能在以后遇到问题,但是以后可以解决。


8
他正在谈论C#8中的Nullable引用类型
Magnus,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.