Почему компилятор C# переводит это!= сравнение, как если бы это было > сравнение?


я совершенно случайно обнаружил, что компилятор C# поворачивает этот метод:

static bool IsNotNull(object obj)
{
    return obj != null;
}

...в это CIL:

.method private hidebysig static bool IsNotNull(object obj) cil managed
{
    ldarg.0   // obj
    ldnull
    cgt.un
    ret
}

...или, если вы предпочитаете смотреть на декомпилированный код C#:

static bool IsNotNull(object obj)
{
    return obj > null;   // (note: this is not a valid C# expression)
}

как получилось, что != переводится как ">"?

1 147

1 ответ:

короткий ответ:

в IL нет инструкции "compare-not-equal", поэтому C# != оператор не имеет точного соответствия и не могут быть переведены буквально.

однако существует инструкция" compare-equal" (ceq, прямая корреспонденция с == оператор), так что в общем случае, x != y переводится как его немного более длинный эквивалент (x == y) == false.

здесь и инструкция "сравнить-больше-чем" в ил (cgt), который позволяет компилятору принимать определенные ярлыки (т. е. генерировать более короткий код IL), одним из которых является сравнение неравенства объектов с null,obj != null, получить перевод, как если бы они были "obj > null".


давайте перейдем к более подробной информации.

если в IL нет инструкции "compare-not-equal", то как компилятор переведет следующий метод?

static bool IsNotEqual(int x, int y)
{
    return x != y;
}

как уже было сказано выше, компилятор превратит элемент x != y на (x == y) == false:

.method private hidebysig static bool IsNotEqual(int32 x, int32 y) cil managed 
{
    ldarg.0   // x
    ldarg.1   // y
    ceq
    ldc.i4.0  // false
    ceq       // (note: two comparisons in total)
    ret
}

оказывается, что компилятор не всегда производит этот довольно длинный шаблон. Давайте посмотрим, что произойдет, когда мы заменим y с константой 0:

static bool IsNotZero(int x)
{
    return x != 0;
}

произведенный IL несколько короче, чем в общем случае:

.method private hidebysig static bool IsNotZero(int32 x) cil managed 
{
    ldarg.0    // x
    ldc.i4.0   // 0
    cgt.un     // (note: just one comparison)
    ret
}

компилятор может воспользоваться тем, что целые числа хранятся в два (где, если результирующие битовые шаблоны интерпретируется как целые числа без знака-вот что такое .un средства - 0 имеет наименьшее возможное значение), поэтому он переводит x == 0 как будто unchecked((uint)x) > 0.

оказывается, компилятор может сделать то же самое для проверки неравенства против null:

static bool IsNotNull(object obj)
{
    return obj != null;
}

компилятор производит почти тот же IL, что и для IsNotZero:

.method private hidebysig static bool IsNotNull(object obj) cil managed 
{
    ldarg.0
    ldnull   // (note: this is the only difference)
    cgt.un
    ret
}

видимо, компилятору позволено предположить, что битовый шаблон null ссылка малейшего шаблон можно на любой объект.

этот ярлык явно упоминается в common Language Infrastructure Annotated Standard (1-е издание от октября 2003 года) (на стр. 491, как сноска таблицы 6-4, "Бинарные сравнения или операции ветвления"):

"cgt.un допускается и проверяется на ObjectRefs (O). Это обычно используется при сравнении ObjectRef с null (нет инструкции "compare-not-equal", которая в противном случае было бы более очевидным решением)."