为什么 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)
}

为什么 !=被翻译成“ >”?

5892 次浏览

简短的回答:

在 IL 中没有“比较不相等”的指令,所以 C # !=操作符没有精确的对应关系,不能从字面上翻译。

然而,有一个“比较相等”的指令(ceq,与 ==操作符直接对应) ,因此在一般情况下,x != y被翻译成略长的等效 (x == y) == false

在 IL (cgt)中有一个 还有“比较大于”指令,它允许编译器采用某些快捷方式(即生成较短的 IL 代码) ,其中之一是对象与 null (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
}

事实证明,编译器并不总是产生这种相当冗长的模式。让我们看看当我们用常量0代替 y时会发生什么:

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

产生的投资限额比一般情况略短:

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

编译器产生几乎与 IsNotZero相同的 IL:

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

显然,编译器可以假定 null引用的位模式是任何对象引用可能的最小位模式。

这个快捷方式在 公共语言基础设施注释标准(2003年10月起第1版)中明确提到(在第491页,作为表6-4“二进制比较或分支操作”的脚注) :

”在 ObjectRefs (O)上允许并可验证 cgt.un。这通常用于将 ObjectRef 与 null 进行比较时(没有“比较不相等”指令,否则这将是一个更明显的解决方案)