私たちが持っているとしましょう:
public void TestIndex1(int index)
{
if(index < 0 || index >= _size)
ThrowHelper.ThrowArgumentOutOfRangeException();
}
public void TestIndex2(int index)
{
if((uint)index >= (uint)_size)
ThrowHelper.ThrowArgumentOutOfRangeException();
}
これらをコンパイルして、ILSpy を見てみましょう。
.method public hidebysig
instance void TestIndex1 (
int32 index
) cil managed
{
IL_0000: ldarg.1
IL_0001: ldc.i4.0
IL_0002: blt.s IL_000d
IL_0004: ldarg.1
IL_0005: ldarg.0
IL_0006: ldfld int32 TempTest.TestClass::_size
IL_000b: bge.s IL_0012
IL_000d: call void TempTest.ThrowHelper::ThrowArgumentOutOfRangeException()
IL_0012: ret
}
.method public hidebysig
instance void TestIndex2 (
int32 index
) cil managed
{
IL_0000: ldarg.1
IL_0001: ldarg.0
IL_0002: ldfld int32 TempTest.TestClass::_size
IL_0007: blt.un.s IL_000e
IL_0009: call void TempTest.ThrowHelper::ThrowArgumentOutOfRangeException()
IL_000e: ret
}
2 番目の方がコードが少なく、分岐が 1 つ少ないことが簡単にわかります。
実際には、キャストはまったくありません。andまたはを使用blt.s
するかどうかの選択があり、後者は渡された整数を符号なしとして扱い、前者はそれらを符号付きとして扱います。bge.s
blt.s.un
(これは CIL の回答を含む C# の質問であるため、CIL に慣れていない方への注意、、、および はそれぞれ、およびbge.s
の「短い」バージョンです。スタックから 2 つの値をポップし、最初の値が 2 番目の値より小さい場合に分岐します。それらを符号付きの値と見なし、スタックの 2 つの値をポップし、それらを符号なしの値と見なしたときに最初の値が 2 番目の値より小さい場合は分岐します)。blt.s
blt.s.un
bge
blt
blt.un
blt
blt.un
それは完全にマイクロオプトですが、マイクロオプトを実行する価値がある場合もあります。さらに、メソッド本体の残りのコードでは、インライン化のジッター制限内に収まるかどうかの違いを意味する可能性があることを考慮してください。範囲外の例外をスローするためのヘルパーが必要な場合は、おそらく、可能な限りインライン化が確実に行われるようにしようとしており、余分な 4 バイトがすべての違いを生む可能性があります。
実際、そのインライン化の違いは、1 つのブランチの削減よりもはるかに大きな問題になる可能性が非常に高くなります。インライン化が確実に行われるようにするためにわざわざ行く価値がある場合はあまりありませんが、そのような頻繁に使用されるクラスのコア メソッドは、List<T>
確かにその 1 つです。