Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IntPtr allow implicit conversion from ulong to long

Tags:

c#

intptr

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 
like image 446
Vince Avatar asked Apr 02 '13 15:04

Vince


2 Answers

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.

like image 80
Joel Rondeau Avatar answered Sep 27 '22 16:09

Joel Rondeau


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;
like image 39
JaredPar Avatar answered Sep 27 '22 17:09

JaredPar