Respuesta corta:
No existe una instrucción "comparar-no-igual" en IL, por lo que el !=
operador C # no tiene correspondencia exacta y no puede traducirse literalmente.
Sin embargo, existe una instrucción "comparar-igual" ( ceq
una correspondencia directa con el ==
operador), por lo que, en el caso general, x != y
se traduce como su equivalente un poco más largo (x == y) == false
.
También hay una instrucción "compare-mayor-que" en IL ( cgt
) que permite al compilador tomar ciertos atajos (es decir, generar un código IL más corto), uno de ellos es que las comparaciones de desigualdad de objetos contra nulos obj != null
, se traducen como si fueran " obj > null
".
Vamos a entrar en más detalles.
Si no hay una instrucción "comparar-no-igual" en IL, entonces, ¿cómo el compilador traducirá el siguiente método?
static bool IsNotEqual(int x, int y)
{
return x != y;
}
Como ya se dijo anteriormente, el compilador convertirá el x != y
en (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
}
Resulta que el compilador no siempre produce este patrón bastante largo. Veamos qué sucede cuando reemplazamos y
con la constante 0:
static bool IsNotZero(int x)
{
return x != 0;
}
La IL producida es algo más corta que en el caso general:
.method private hidebysig static bool IsNotZero(int32 x) cil managed
{
ldarg.0 // x
ldc.i4.0 // 0
cgt.un // (note: just one comparison)
ret
}
El compilador puede aprovechar el hecho de que los enteros con signo se almacenan en el complemento de dos (donde, si los patrones de bits resultantes se interpretan como enteros sin signo, eso es lo que .un
significa - 0 tiene el valor más pequeño posible), por lo que se traduce x == 0
como si fuera unchecked((uint)x) > 0
.
Resulta que el compilador puede hacer lo mismo para las comprobaciones de desigualdad contra null
:
static bool IsNotNull(object obj)
{
return obj != null;
}
El compilador produce casi el mismo IL que para IsNotZero
:
.method private hidebysig static bool IsNotNull(object obj) cil managed
{
ldarg.0
ldnull // (note: this is the only difference)
cgt.un
ret
}
Aparentemente, el compilador puede asumir que el patrón de bits de la null
referencia es el patrón de bits más pequeño posible para cualquier referencia de objeto.
Este acceso directo se menciona explícitamente en el Estándar Anotado de Infraestructura de Lenguaje Común (primera edición de octubre de 2003) (en la página 491, como una nota al pie de la Tabla 6-4, "Comparaciones binarias u operaciones de sucursal"):
" cgt.un
está permitido y es verificable en ObjectRefs (O). Esto se usa comúnmente cuando se compara un ObjectRef con un valor nulo (no existe una instrucción" comparar-no-igual ", que de lo contrario sería una solución más obvia)".
int
el rango tengan la misma representaciónint
que enuint
. Ese es un requisito mucho más débil que el complemento de dos.