Ответ 1
Краткий ответ:
В IL нет инструкции "сравнивать не равные", поэтому оператор С# !=
не имеет точного соответствия и не может быть переведен буквально.
Однако существует "сравниваемая поровну" инструкция (ceq
, прямое соответствие оператору ==
), поэтому в общем случае x != y
переводится как его несколько более длинный эквивалент (x == y) == false
.
В IL (cgt
) имеется команда "compare-more-than", которая позволяет компилятору принимать определенные ярлыки (т.е. генерировать более короткий IL-код), один из которых заключается в сравнении неравенств объектов с нулем, obj != null
, переводится так, как будто они были "obj > null
".
Давайте рассмотрим более подробно.
Если в IL нет инструкции "сравните-не-равно", то как будет преобразован следующий метод компилятором?
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
является наименьшим битовым шаблоном, возможным для любой ссылки на объект.
Этот ярлык явно упоминается в стандартном аннотированном стандарте языковой инфраструктуры (1-е издание с октября 2003 года) (на странице 491, в качестве сноски к Таблице 6-4, "Бинарные сравнения или операции ветки" ):
"
cgt.un
разрешен и проверен в ObjectRefs (O). Это обычно используется при сравнении ObjectRef с нулевым значением (нет инструкции" сравните-не-равно ", которая в противном случае была бы более очевидным решением)."