C#编译器为何将此!=比较翻译为>比较?

问题描述 投票:147回答:1

我完全有机会发现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)
}

!=为何被翻译为“ >”?

c# cil il notnull binary-operators
1个回答
201
投票

简短回答:

IL中没有“ compare-not-equal”指令,因此C#!=运算符没有确切的对应关系,因此不能按字面意义进行翻译。

但是有一个“比较等价”指令(ceq,与==运算符直接对应,因此,在一般情况下,x != y像其稍长的等价项(x == y) == false一样被翻译。

[ IL(cgt)中有一个“ compare-greater-than”指令,它允许编译器采用某些快捷方式(即,生成较短的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; }

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.