147

我纯属偶然发现 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)
}

怎么会!=被翻译成“ >”?

4

1 回答 1

201

简短的回答:

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
}

事实证明,编译器并不总是产生这种相当冗长的模式。让我们看看当我们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;
}

编译器产生与 for 几乎相同的 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引用的位模式是任何对象引用可能的最小位模式。

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

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

于 2015-02-28T12:43:57.077 回答