Let's say we have:
public void TestIndex1(int index)
{
if(index < 0 || index >= _size)
ThrowHelper.ThrowArgumentOutOfRangeException();
}
public void TestIndex2(int index)
{
if((uint)index >= (uint)_size)
ThrowHelper.ThrowArgumentOutOfRangeException();
}
Let's compile these and look at 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
}
It's easy to see that the second has less code, with one less branch.
Really, there's no cast at all, there's the choice of whether to use blt.s
and bge.s
or to use blt.s.un
, where the latter treats the integers passed as unsigned while the former treats them as signed.
(Note for those not familiar with CIL, since this is a C# question with a CIL answer, bge.s
, blt.s
and blt.s.un
are the "short" versions of bge
, blt
and blt.un
respectively. blt
pops two values off the stack and branches if the first is less than the second when considering them as signed values while blt.un
pops two values of the stack and branches if the first is less than the second when considering them as unsigned values).
It's utterly a micro-opt, but there are times when micro-opt's are worth doing. Consider further, that with the rest of the code in the method body this could mean the difference between something falling inside the jitters limits for inlining or not, and if they're bothering to have a helper for throwing out of range exceptions they're probably attempting to ensure inlining happens if at all possible, and the extra 4 bytes could make all the difference.
Indeed, it's quite likely for that inlining difference to be a much bigger deal than the reduction of one branch. There aren't a lot of times when going out of your way to ensure inlining happens is worth it, but a core method of a class of such heavy use as List<T>
would certainly be one of them.
int
touint
): codeproject.com/Articles/8052/… – Finespununchecked { ... }
around the whole block, because you don't know when one of your colleague will change a project from unchecked to checked :) – Guanabana_size
can never be negative. – Knockknee