class A
{
public static explicit operator A(long mm)
{
return null;
}
}
UInt64 ul = UInt64.MaxValue;
IntPtr ptr = (IntPtr)ul;//no error
A a = (A)ul;//Cannot convert type 'ulong' to 'A'
why do IntPtr allow the behavior?
following is IL code:
.entrypoint
.maxstack 1
.locals init (
[0] uint64 ul,
[1] native int ptr)
L_0000: nop
L_0001: ldc.i4.m1
L_0002: conv.i8
L_0003: stloc.0
L_0004: ldloc.0
L_0005: call native int [mscorlib]System.IntPtr::op_Explicit(int64)
L_000a: stloc.1
L_000b: ret
I agree that this seemed a bit odd, so I ran a couple of tests.
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
.
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.
The IntPtr
and UIntPtr
types are just managed representations of an address which itself is a number. Hence it provides conversions between values which are logically numeric and of the same signed / unsignedness.
In this case UIntPtr
is unsigned and hence only provides conversions to unsigned numeric values like ulong
. This is incompatible with the explicit operator on A
which accepts a long
(signed) value.
You need to either add an extra operator for ulong
or do an explicit cast to long
from UIntPtr
A a = (A)(long)ul;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With