GetHashCode()
- это виртуальный метод, переопределенный в Nullable<T>
: когда он вызывается для значения Nullable<T>
, используется реализация Nullable<T>
без каких-либо рамок.
GetType()
не является виртуальным методом, что означает, что при его вызове значение помещается в первую очередь ... и помещается в пустое значение, равное нулю, приводит к пустой ссылке - отсюда исключение. Мы можем видеть это из 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: инструкция box
перед инструкцией callvirt
в L_000f.
Теперь сравните это с эквивалентным кодом, вызывающим 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
}
На этот раз у нас есть constrained
инструкция / префикс перед callvirt
, что по сути означает «вам не нужно боксировать, когда вы вызываете виртуальный метод». Из OpCodes.Constrained
документации:
Ограниченный префикс предназначен для того, чтобы позволить инструкциям callvirt выполняться единообразным образом, независимо от того, является ли thisType типом значения или ссылочным типом.
(перейдите по ссылке для получения дополнительной информации.)
Обратите внимание, что то, как работает бокс типов значений 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>
.