【问题标题】:When exactly do nullable types throw exceptions?可空类型究竟什么时候抛出异常?
【发布时间】:2018-10-12 04:42:40
【问题描述】:

考虑以下代码:

int? x = null;
Console.Write ("Hashcode: ");
Console.WriteLine(x.GetHashCode());
Console.Write("Type: ");
Console.WriteLine(x.GetType());

在执行时,它写入哈希码是0,但在尝试确定x 的类型时以NullReferenceException 失败。 我知道在可空类型上调用的方法实际上是在底层值上调用的,所以我预计程序在 x.GetHashCode() 期间会失败。

那么,这两种方法的根本区别是什么?为什么第一种方法没有失败?

【问题讨论】:

  • 我能找到的唯一区别是 GetHashCodevirtual...
  • ILSpy 是一个方便的小工具,可以帮助回答这类问题。
  • 我觉得奇怪的是 GetType()Nullable<int> 返回 System.Int32,而不是 System.Nullable<System.Int32>
  • 另外值得注意的是int? x = nullNullable<int> x = new Nullable<int>(null) 的语法糖。所以,x 是一个实际的对象,而不是一个空引用。
  • 参考源 - github.com/Microsoft/referencesource/blob/master/mscorlib/… - 没有显示 GetType 正在被处理,文档也没有详细说明 - docs.microsoft.com/en-us/dotnet/api/…

标签: c# .net nullreferenceexception nullable


【解决方案1】:

这是因为int? x = null; 本质上创建了一个值类型为System.Nullable<int> 的实例,具有“内部”null 值(您可以通过.HasVaue 属性检查它)。当GetHashCode被调用时,覆盖Nullable<int>.GetHashCode是候选方法(因为方法是虚的),现在我们有了Nullable<int>的实例,并执行它的实例方法,完美。

在调用GetType时,该方法是非虚拟的,所以Nullable<int>的实例首先被装箱为System.Object,根据the document,装箱值为null,因此NullReferenceException .

【讨论】:

  • 所有这些都符合我们看到的行为,但它记录在哪里?您链接到的页面详细说明了Nullable<T> 的装箱行为如何工作,但并没有说GetType() 需要装箱操作。我猜我们需要找到有关继承方法如何作用于值类型的文档,以找到相关部分。
  • @LasseVågsætherKarlsen 这是因为GetType 是在object 而不是struct 上定义的,这会导致隐式装箱。您可以通过分配int? x = 1 然后调用GetType() 来验证。结果是System.Int32不是 Nullable<T>
  • @LasseVågsætherKarlsen 您可以阅读 C# 语言规范第 4.3 节“装箱和拆箱”了解更多详细信息。
  • 如果我输入int? x = 空;然后我尝试阅读它: x.Value 然后我得到异常,而不是空“值”。如果我检查“x”本身是否为空,那么答案是肯定的。
  • @Eru 我在答案中使用了不正确的表达方式,谢谢指出。
【解决方案2】:

澄清Danny Chen的正确答案:

  • Nullable<T> 是一个值类型。值类型由一个 bool 组成,表示 null(false 表示 null)和一个 T,即值。
  • 与所有其他值类型不同,可为空的类型不会装箱到装箱的Nullable<T>。它们将框到框的 T 或空引用。
  • 由值类型S 实现的方法的实现就像它有一个不可见的ref S 参数一样;这就是this 的传递方式。
  • 由引用类型C 实现的方法的实现就像有一个不可见的C 参数一样;这就是this 的传递方式。
  • 有趣的情况是在引用基类中定义并被从基类继承的结构覆盖的虚方法。

现在您有足够的信息来推断会发生什么。 GetHashCode 是虚拟的并被Nullable<T> 覆盖,所以当你调用它时,你调用它就好像this 有一个不可见的ref Nullable<T> 参数。没有拳击发生。

GetType 不是虚拟的,因此不能被覆盖,并且在object 上定义。因此它期望 object 对应 this,当在 Nullable<T> 上调用时,接收器必须被装箱,因此可以装箱为空,因此可以抛出。

如果您调用((object)x).GetHashCode(),那么您会看到一个异常。

【讨论】:

  • 关于隐形ref SC 的第三和第四个要点有点不清楚。你能扩展它们吗?
  • @Nisarg:当你有class C { void M(int x) { ... } } 并且你有C c = new C(); c.M(123); 时,不知何故c 必须在M 中变成this。发生的方式是调用逻辑上与您有class C { static void M(C _this, int x) { ... }} 和调用C.M(c, 123) 相同。 this 在逻辑上只是另一个论点。对于结构类似,除了结构this 是变量的ref 别名。
【解决方案3】:

Nullable<T>.GetHashCode()的实现如下:

public override int GetHashCode()
{
    if (!this.HasValue)
    {
        return 0;
    }
    return this.value.GetHashCode();
}

所以,当值为空时,它总是会得到你0

x.GetType()null.GetType() 相同,会抛出 Object reference not set to an instance of an object

【讨论】:

  • 所以,GetHashCode 是在 Nullable 上调用的,而不是在它包含的值上?为什么会这样?
  • 为什么 x.GetType()null.GetType() 一样,因为实际上你有 (struct).GetType()Nullable<T> 是一个结构体,值类型。
  • 您能否也发布GetType 的实现,以防万一它为答案添加更多细节?
【解决方案4】:

似乎 GetHashCode 有一个空检查。 (使用JetBrains查看防御)

public override int GetHashCode()
{
  if (!this.hasValue)
    return 0;
  return this.value.GetHashCode();
}

【讨论】:

  • GetHashCode() 的实现只有在确定完全可以调用 GetHashCode() 后才会发挥作用。
猜你喜欢
  • 2011-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多