为什么C#编译器将此变换!=比较,就像它是>比较呢?

时间:2015-02-28 12:43:57

标签: c# cil il notnull binary-operators

我有机会发现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 个答案:

答案 0 :(得分:201)

简答:

IL中没有“比较不等于”指令,因此C#!=运算符没有完全对应,无法按字面翻译。

然而,有一条“比较相等”的指令(ceq,与==运算符直接对应),因此在一般情况下,x != y会被翻译为略长一些相当于(x == y) == false

IL中的“compare-greater-than”指令(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
}

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

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
}

编译器可以利用有符号整数存储在two's complement中的事实(如果结果位模式被解释为无符号整数,那么.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引用的位模式是任何对象引用可能的最小位模式。

Common Language Infrastructure Annotated Standard (1st edition from Oct 2003)(第491页,表6-4“二进制比较或分支操作”的脚注)中明确提到了此快捷方式:

  

cgt.un在ObjectRefs(O)上是允许和可验证的。这通常在将ObjectRef与null进行比较时使用(没有”compare-not-equal“指令,否则将是更明显的解决方案)。“