【问题标题】:Why shouldn't I always use nullable types in C#为什么我不应该总是在 C# 中使用可为空的类型
【发布时间】:2009-05-06 16:50:47
【问题描述】:

自从 .net 2.0 中引入这个概念以来,我一直在寻找一些好的指导。

为什么我想在 c# 中使用不可为空的数据类型? (一个更好的问题是我为什么不默认选择可空类型,并且仅在明确有意义时才使用不可空类型。)

选择一个可以为空的数据类型而不是它的不可为空的对等体是否有“显着”的性能影响?

我更喜欢对照 null 而不是 Guid.empty、string.empty、DateTime.MinValue、

有没有人总是(最总是)选择可空类型而不是不可空类型?

感谢您的宝贵时间,

【问题讨论】:

  • Jon Skeet 关于 System.Nullable 是一个结构是正确的。所以它会被分配给栈,而不是堆。
  • 关于性能打击的问题只有你自己来回答;您是知道“重要”对您的客户意味着什么的人。但是命中应该是最小的——一个可为空的 int 与一个 int 加一个指示它是否为空的 bool 标志几乎完全相同; Nullable 类型只是将 int 与 flag 打包的便捷方式。

标签: c# null c#-2.0 nullable


【解决方案1】:

您不应该总是使用可空类型的原因是有时您能够保证一个值被初始化。而且您应该尝试设计您的代码,以便尽可能多地出现这种情况。如果一个值不可能未初始化,那么 null 就没有理由成为它的合法值。作为一个非常简单的例子,考虑一下:

List<int> list = new List<int>()
int c = list.Count;

这是始终有效的。 c 不可能未初始化。如果将其转换为int?,您实际上是在告诉代码的读者“此值可能为空。请务必在使用前进行检查”。但是我们知道这永远不会发生,那么为什么不在代码中公开这个保证呢?

在值是可选的情况下,你是绝对正确的。如果我们有一个函数可能返回也可能不返回字符串,则返回 null。不要返回 string.Empty()。不要返回“魔法值”。

但并非所有值都是可选的。将所有内容都设为可选会使您的其余代码变得更加复杂(它添加了另一个必须处理的代码路径)。

如果您可以明确保证此值始终有效,那么为什么要丢弃此信息?这就是您通过将其设为可为空的类型所做的。现在该值可能存在也可能不存在,任何使用该值的人都必须处理这两种情况。但你知道,一开始只有一种情况是可能的。因此,您的代码的用户可以帮个忙,并在您的代码中反映这一事实。然后,您的代码的任何用户都可以依赖该值是有效的,他们只需要处理一个案例而不是两个案例。

【讨论】:

  • 这是对这个问题的一个非常好的答案,但它让我想修改它。为什么我不应该总是对企业应用程序中的业务域类属性使用 Nullable 类型?我想我可能大部分时间都可以。感谢您的宝贵时间。
  • 我认为同样的经验法则也适用于那里。你能保证财产永远有效吗? (您的对象可能总是有一个 ID,因此将其设为可空没有意义。但其他属性可能存在也可能不存在,具体取决于对象的状态。但请记住,if 您可以创建一个value 不可为空,这使得它在未来的工作变得更好,因为用户不必检查它是否为 null。所以看看是否可以保证该属性是有效的,如果可以,不要t 使它可以为空。
  • 如果我们获得了 Spec# 功能来将引用类型声明为不可为空,那不是很酷吗? string! 将代表一个不可能为空的字符串。
  • 换个角度看,你的意思是,如果我们有一个像List&lt;int&gt; 这样的对象,我们总是可以安全地假设它会在我们需要使用它时被初始化并有效。但是为了使其有效,即使数据库返回 NULL,我们也需要一个层将其初始化为空,以便我们可以执行操作而不会出现 NULL REFERENCE EXCEPTIONS。但这只有在团队中的每个人都严格确保该属性永远不会是 NULL TYPE 时才有效,即使数据库返回 NULL。这永远不会发生,因为他们说 NULL 是有效的,处理它。
【解决方案2】:

因为总是要检查可空类型是否为null 很不方便。

显然,在某些情况下,值是真正可选的,在这些情况下,使用可为空的类型而不是幻数等是有意义的,但我会尽可能避免使用它们。

// nice and simple, this will always work
int a = myInt;

// compiler won't let you do this
int b = myNullableInt;

// compiler allows these, but causes runtime error if myNullableInt is null
int c = (int)myNullableInt;
int d = myNullableInt.Value;    

// instead you need to do something like these, cumbersome and less readable
int e = myNullableInt ?? defaultValue;
int f = myNullableInt.HasValue ? myNullableInt : GetValueFromSomewhere();

【讨论】:

  • 我想这是我的观点。可能 myNonNullableInt 尚未设置,并且其中包含默认的 0 值,这对于我的业务域而言与 null 一样无效。所以无论如何我都要检查一下。我更喜欢 myNullableInt != null 到 myNonNullableInt > 0
  • 即使对于非本地范围,仅在适当的情况下使用可为空的类型是有意义的:如果一个值实际上永远不可能为空,那么为什么要将其声明为可空,这意味着处理该类型的每一段代码必须做额外的检查等。
【解决方案3】:

我认为语言设计者认为“默认情况下引用类型可以为空”是一个错误,不可为空是唯一合理的默认设置,您应该选择为空。 (在许多现代函数式语言中就是这样。)“null”通常是一堆麻烦。

【讨论】:

  • 你说语言设计者想要“远离”空性,但他们实际上为值类型添加了可空性,以前不能为空。并且引用类型默认仍为 null,无法退出。我不明白你的推理。
  • 这里有一个更好的方式来描述一些语言设计者的立场。我们有两种类型:值类型和引用类型。我们有两种类型:可空类型和不可空类型。我们从一个类型系统开始,其中所有引用类型都可以为空,所有值类型都不能为空。然后我们添加了可为空的值类型。但是现在添加不可为空的引用类型非常困难。最好首先设计类型系统以允许所有四种可能性。
  • 是的,Eric 说的很棒。我将补充一点,对于某些互操作场景(例如 SQL 数据库),值类型的可空性是一个特定的胜利。但从一般软件工程的角度来看,大多数类型的不可为空性是大多数代码的普遍胜利。
  • .net 框架假定结构、类字段和数组元素可以通过在创建底层结构、类或数组时用零字节填充它们来实现;这与 C++ 不同,在 C++ 中,数组的创建首先调用所有元素的构造函数,如果其中任何一个失败,则调用已构造的任何元素的析构函数。这个假设极大地简化了框架,但它要求所有类类型的存储位置都有一个默认值,除了null 之外没有其他任何东西真正有意义。
【解决方案4】:

您似乎有两个不同的问题...

为什么我想在 C# 中使用不可为空的数据类型?

很简单,因为编译器保证您所依赖的值类型数据实际上具有值!

为什么我不默认选择可空类型,而只在明确有意义时才使用不可空类型?

正如 Joel 已经提到的,一个类型只能是 null 如果它是一个引用类型。编译器保证值类型具有值。如果您的程序依赖于一个变量来获得一个值,那么这就是您希望通过不选择可空类型来实现的行为。

当然,当您的数据来自您的程序之外的任何地方时,所有的赌注都将被取消。最好的例子来自数据库。数据库字段可以是null,因此您希望您的程序变量模仿这个值 - 而不仅仅是创建一个“代表”null 的“神奇”值(即 -1、0 或其他值)。您可以使用可为空的类型来执行此操作。

【讨论】:

  • 正如其他答案/cmets 中提到的,可空类型 值类型,但不能保证它们具有值。 (这就是 HasValue 属性的用途!)
  • 没关系,他的意思是“不可为空的值类型”:)
【解决方案5】:

虽然空值可以方便地用作“尚未初始化”或“未指定”值,但它们会使代码更复杂,主要是因为您重载了 null 以及变量(number-or-null vs. just-a-number)。

NULL 值受到许多数据库设计人员和 SQL 数据库程序员的青睐,但只要稍微改变一下对问题的思考,您就可以摆脱 null 值并实际上拥有更简单和更可靠的代码(例如,不用担心 NullReferenceExceptions )。

实际上对“T!”的需求很大使任何引用类型不可为空的运算符,类似于 "T?"使值类型可以为空,C# 的发明者 Anders Hejlsberg 希望他能包含这种能力。

另请参阅问题,Why is “null” present in C# and java?

【讨论】:

  • 不可为空的概念几乎已被正式安排在 C# 8 中。据我所知,这不会与某些“T!”有关。运算符,而是所有引用类型都将是“不可为空”的,直到绕过/标记为可空。更多:msdn.microsoft.com/en-us/magazine/mt829270.aspx
【解决方案6】:

我倾向于在任何有意义的地方使用 Nullable 类型——在出现问题之前我不会关心性能,然后我会修复它存在的几个区域并完成它。

但是,我还发现,一般来说,我的大多数值最终都是不可为空的。事实上,很多时候我实际上都想要一个 NotNullable,当我得到 null 时,我可以将它与引用类型一起使用来找出 null 问题,而不是以后尝试使用它时。

【讨论】:

    【解决方案7】:

    唯一应该使用 Nullable 类型的情况是,数据库表中的某个字段绝对需要应用程序在某个时刻发送或接收空值。即使在这种情况下,也应该始终尝试找到使用 Nullable 类型的方法。 Bool 并不总是你最好的朋友。

    【讨论】:

      猜你喜欢
      • 2022-10-25
      • 2013-06-07
      • 2016-02-18
      • 2021-10-25
      相关资源
      最近更新 更多