假设我们有:
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
}
很容易看出,第二个代码更少,分支更少。
真的,根本没有强制转换,可以选择使用blt.s
和bge.s
还是使用blt.s.un
,后者将传递的整数视为无符号,而前者将它们视为有符号。
(注意那些不熟悉 CIL 的人,因为这是一个带有 CIL 答案的 C# 问题bge.s
,blt.s
和分别是和blt.s.un
的“短”版本。如果第一个值小于第二个值,则从堆栈中弹出两个值并bge
分支将它们视为有符号值,同时弹出堆栈的两个值并在将它们视为无符号值时如果第一个小于第二个则分支)。blt
blt.un
blt
blt.un
这完全是一个微型选择,但有时微型选择是值得的。进一步考虑,对于方法主体中的其余代码,这可能意味着是否在内联的抖动限制内的东西之间的差异,如果他们不想有一个帮助器来抛出超出范围的异常,他们是如果可能的话,可能会尝试确保内联发生,而额外的 4 个字节可能会产生重大影响。
实际上,这种内联差异很可能比减少一个分支更大。没有多少次会竭尽全力确保内联发生是值得的,但是一个如此大量使用的类的核心方法List<T>
肯定是其中之一。