почему n.GetHashCode () работает, а n.GetType () генерирует исключение? - PullRequest
9 голосов
/ 05 апреля 2011

Я учу себя 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 ()?

Спасибо за помощь,

Джон.

1 Ответ

14 голосов
/ 05 апреля 2011

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>.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...