为什么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) }
为什么!=
被翻译成“ >
”?
简短的回答:
在IL中没有“比较 – 不等于”的指令,所以C# !=
操作符没有确切的对应关系,不能直接翻译。
然而,有一个“compare-equal”指令( 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 }
事实certificate,编译器并不总是产生这种相当冗长的模式。 让我们看看当我们用常量0replacey
时会发生什么:
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
。
事实certificate编译器可以对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,2003年10月第一版) (作为表6-4“二进制比较或分支操作”的脚注,第491页)中明确提到了此捷径:
“ObjectRefs(O)允许和validation
cgt.un
当比较一个ObjectRef和null(没有”compare-not-equal“指令,这是一个更明显的解决scheme)时,这是常用的。