为什么/ .Net允许我否定Nullable <t>是否为null?</t>

时间:2010-12-06 17:15:04

标签: .net nullable

我发现自己正盯着看起来与此相似的代码:

private double? _foo;

public double? Foo
{
    get { return _foo; }
    set { Baz(-value); }
}

public void Baz(double? value)
{
    // Perform stuff with value, including null checks
}

在测试中,我有这个:

[Test]
public void Foo_test()
{
    ...
    Foo = null;
    ...
}

我预计测试会失败(抛出),因为我认为你真的不能将一元减号应用于null,我认为这会在Foo的二传手中发生。但是,意外发生了,测试失败

我可能会对这是为什么会发生这种情况做出明智的猜测,但我认为如果真正知道发生了什么的人能够启发我的话会更好。

(在有人指出显而易见的事实之前:是的,否定肯定会被推到Baz的内部。)

修改并跟进

所以,我很感谢Jon Skeet和JaredPar的投入,他们都回答了我的问题的 How 部分。经过一些谷歌搜索后,我找到了this blog post by Eric Lippert explaining the mathematical definition of 'lifted',这很有道理。我还阅读了语言规范的相关部分,这些部分非常直接地解释了c#中提升的机制。

具体来说,我现在知道在提升过程的早期,对操作数进行空检查,如果操作数为null,则null将是应用运算符的结果(在我的情况下是一元减去)。因此-nullnull

现在,问题的“为什么”部分。如果我在operator -上定义我自己的(一元)class(其中我否定了一个包裹的值) ),并在空引用上调用此运算符,我将得到NullReferenceException为什么'提升'表现不同?我实际上对这个设计决策背后的推理感兴趣。 我绝对反对这个决定。

4 个答案:

答案 0 :(得分:12)

  

为什么'提升'表现不同?我实际上对这个设计决定背后的推理感兴趣。

好吧,首先,你不应该总是得到一个空引用异常。静态调度操作员调用;它不需要进行空检查。这完全合法:

class P
{
  public static P operator -(P p)
  { return p; }

  static void Main()
  {
    P p1 = null;
    P p2 = -p1;
    Console.WriteLine(p2 == null);
  }
}

我真的认为你的问题意味着“为什么可空值的类型会将自动从非可空操作解除为可空操作,但是如果你有参考类型要求你在运算符中编写特定的逻辑想要可空的语义?“

这有点乱。

问题是,引用类型总是可以开头,所以你总是可以自己编写代码。由于后来添加了可空值类型,我们添加了一种机制,以便所有现有的非可空操作符可以突然与可空类型一起正常工作,这样您就不必自己编写所有这些无聊的代码。

但是有一个更普遍的问题。更普遍的问题是我们将“null”混为一谈意味着两件事。在引用类型世界中,null表示“我不引用任何对象”。在值类型世界中,null表示它在数据库中的含义:此数量可能具有值但我们不知道它是什么。如果所有销售人员都没有报告他们的结果,11月的销售数字是多少?肯定有一个美元价值,但我们不知道它是什么,所以我们将其标记为空。

提升算术旨在处理“数据库空”语义; null加十二为空。你不知道的东西加上十二是你不知道的其他东西。不幸的是,由于我们没有在C#中添加可空算术直到版本2,因此已经存在将null视为“有一个引用无对象的对象引用”语义的所有设备。

如果我们从头开始设计整个事物,我怀疑(1)会有可为空的引用类型,不可为空的引用类型,可空的值类型和不可空的值类型,以及(2)会有更多明确定义空引用和空值之间的差异,或者它们的语义在算术时更加一致,以及(3)将所有非可空操作自动解除为可空操作。

请注意VB/VBScript implement the more clear distinction by having two separate values: Null, which is database null, and Nothing, which is reference null

答案 1 :(得分:9)

它可以否定它,因为这是“提升”一元运算符的定义方式...如果执行任何操作,如否定,加法,减法等,并且任一操作数为空,结果也为空。

有关详细信息,请参阅语言规范的第7.3.7节。

在这种特殊情况下:

  

对于一元运算符(+,++, - , - ,!,〜),如果操作数和结果类型都是非可空值类型,则存在一个提升形式的运算符。提升形式是通过添加一个?操作数和结果类型的修饰符。如果操作数为null,则提升的运算符将生成空值。否则,提升的运算符解包操作数,应用基础运算符,并包装结果。

请注意,这是针对您正在使用的语言而定的 - 特别是&amp;和|运算符在C#中的bool?值与在VB中的值不同。

答案 2 :(得分:4)

这只是C#中可空值的一个特性。如果任何可以为空的输入为null,则将预定义的一元和二元运算符集应用于可空值将生成null

操作员部分的更多信息

修改

正如Jon指出的那样,这个规则不适用于||true等具有非可空返回类型的运算符。

答案 3 :(得分:0)

将此表达式写入调试监视或立即窗口:

-((int?)null)

结果仍为null,所以很明显是允许的。