为什么 n.GetHashCode() 有效,但 n.GetType() 抛出和异常

本文关键字:异常 GetType GetHashCode 有效 为什么 | 更新日期: 2023-09-27 17:48:55

我正在自学C#(我还不太了解)。在这个简单的示例中:

bool?          n = null;
Console.WriteLine("n               = {0}", n);
Console.WriteLine("n.ToString()    = {0}", n.ToString());
Console.WriteLine("n.GetHashCode() = {0}", n.GetHashCode());
// this next statement causes a run time exception
Console.WriteLine("n.GetType()     = {0}", n.GetType());

直觉上,我理解为什么 GetType() 方法会抛出异常。 实例 n 是空的,这可以解释这一点,但是,为什么在使用 n.GetHashCode() 和 ToString() 时,出于同样的原因我没有收到异常?

谢谢你的帮助,

John。

为什么 n.GetHashCode() 有效,但 n.GetType() 抛出和异常

GetHashCode() 是一个在 Nullable<T> 中被覆盖的虚拟方法:当它被调用Nullable<T>值时,将使用Nullable<T>实现,没有任何装箱。

GetType() 不是虚拟方法,这意味着当调用它时,该值首先装箱...将"null"可为空的值装箱会导致空引用 - 因此出现异常。我们可以从 IL 中看到这一点:

static void Main()
{
    bool? x = null;
    Type t = x.GetType();
}

编译为:

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] valuetype [mscorlib]System.Nullable`1<bool> nullable,
        [1] class [mscorlib]System.Type 'type')
    L_0000: nop 
    L_0001: ldloca.s nullable
    L_0003: initobj [mscorlib]System.Nullable`1<bool>
    L_0009: ldloc.0 
    L_000a: box [mscorlib]System.Nullable`1<bool>
    L_000f: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
    L_0014: stloc.1 
    L_0015: ret 
}

这里重要的一点是L_000a:L_000f callvirt指令之前的box指令。

现在将其与调用GetHashCode的等效代码进行比较:

static void Main()
{
    bool? x = null;
    int hash = x.GetHashCode();
}

编译为:

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] valuetype [mscorlib]System.Nullable`1<bool> nullable,
        [1] int32 num)
    L_0000: nop 
    L_0001: ldloca.s nullable
    L_0003: initobj [mscorlib]System.Nullable`1<bool>
    L_0009: ldloca.s nullable
    L_000b: constrained [mscorlib]System.Nullable`1<bool>
    L_0011: callvirt instance int32 [mscorlib]System.Object::GetHashCode()
    L_0016: stloc.1 
    L_0017: ret 
}

这次我们在 callvirt 之前有一个constrained指令/前缀,这本质上意味着"调用虚拟方法时不需要装箱"。从OpCodes.Constrained文档中:

受约束的前缀旨在允许以统一的方式发出 callvirt 指令,而与 thisType 是值类型还是引用类型无关。

(点击链接了解更多信息。

请注意,可为 null 的值类型的工作方式也意味着即使对于非 null 值,也不会得到Nullable<T>。例如,考虑:

int? x = 10;
Type t = x.GetType();
Console.WriteLine(t == typeof(int?)); // Prints False
Console.WriteLine(t == typeof(int)); // Prints True

因此,您得到的类型是所涉及的不可为空的类型。对object.GetType()的调用永远不会返回Nullable<T>类型。