私は、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には「compare-not-equal」命令がないため、C#!=
演算子には正確な対応関係がなく、文字どおりに翻訳できません。
ただし、「compare-equal」命令(ceq
、==
演算子に直接対応)があるため、一般的な場合、x != y
は、少し長い同等の(x == y) == false
のように変換されます。
alsoIL(cgt
)には「compare-greater-than」命令があり、コンパイラが特定のショートカット(つまり短いILコードを生成します)。1つは、オブジェクトと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
}
コンパイラーがこのかなり長いパターンを常に生成するとは限りません。 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
}
コンパイラは、符号付き整数が 2の補数 に格納されているという事実を利用できます(ここで、結果のビットパターンが符号なし整数として解釈される場合—それは.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(2003年10月からの第1版) (491ページ、表6-4「バイナリ比較またはブランチ操作」の脚注として)に明示的に記載されています。
"
cgt.un
はObjectRef(O)で許可および検証可能です。これは、ObjectRefとnullを比較するときによく使用されます(そうでない場合、「compare-not-equal」命令はありません。明らかな解決策)。 "