I agree that this seemed a bit odd, so I ran a couple of tests.
Test #1: Do a cast of a ulong and a long
ulong ul = UInt64.MaxValue;
long l = Int64.MaxValue;
IntPtr ulptr = (IntPtr)ul;
IntPtr lptr = (IntPtr)l;
Because the IntPtr
cast states that it may throw an OverflowException
, I was expecting the (IntPtr)ul
cast to throw an exception. It did not. Imagine my surprise when the (IntPtr)l
cast threw an OverflowException
. Looking into this, I saw that my project was set to compile for x86
, so the exception now made sense -- Int64.MaxValue
is too large to fit into a 32-bit IntPtr
.
Test #2: put a checked
block around the same code.
Now, I really expected the (IntPtr)ul
cast to throw an exception, and it did.
This made me wonder what was going on with the first cast. Using ildasm
on the unchecked code leads to the following:
IL_0000: nop
IL_0001: ldc.i4.m1
IL_0002: conv.i8
IL_0003: stloc.0
IL_0004: ldc.i8 0x7fffffffffffffff
IL_000d: stloc.1
IL_000e: ldloc.0
IL_000f: call native int [mscorlib]System.IntPtr::op_Explicit(int64)
IL_0014: stloc.2
IL_0015: ldloc.1
IL_0016: call native int [mscorlib]System.IntPtr::op_Explicit(int64)
So -1 is put on the stack and converted to an int64
, but there's no extra conversion from an unsigned to a signed int64
.
The checked
version is slightly different:
IL_0000: nop
IL_0001: nop
IL_0002: ldc.i4.m1
IL_0003: conv.i8
IL_0004: stloc.0
IL_0005: ldc.i8 0x7fffffffffffffff
IL_000e: stloc.1
IL_000f: ldloc.0
IL_0010: conv.ovf.i8.un
IL_0011: call native int [mscorlib]System.IntPtr::op_Explicit(int64)
IL_0016: stloc.2
IL_0017: ldloc.1
IL_0018: call native int [mscorlib]System.IntPtr::op_Explicit(int64)
Now there is a cast from unsigned to signed, which is necessary in case of overflow.
Unfortunately, this doesn't answer the original question.
Update: I removed the portion of the answer that was incorrect, thus leaving no actual answer. However, I expect it is helpful so I have not deleted the entire answer.