Mahmoud Al-Qudsi
Mahmoud Al-Qudsi

Reputation: 29579

Cannot convert ref enum to ref dynamic?

Given a function declaration

dynamic DoSomething(dynamic inputObject)

I can call it with an enum as inputObject:

MyEnum myEnum;
myEnum = DoSomething(myEnum);

But for some reason if the function were to declare inputObject as type ref dynamic instead of dynamic:

dynamic DoSomething(ref dynamic inputObject)

The following does not work due to invalid conversion:

MyEnum myEnum;
DoSomething(ref myEnum);

Is there something special about Enums that prevents me from using them with ref dynamic?

Upvotes: 3

Views: 1699

Answers (2)

codingadventures
codingadventures

Reputation: 2952

The only way to pass as a reference is convert the myEnum in a dynamic type and then pass it by reference. I think we should give a closer look at the generated IL to understand what's going on under the hood. Let's discover why and analyze this program:

 enum MyEnum{
     A,B
}

void Main()
{
    MyEnum  myEnum = MyEnum.B; //Assign a variable

    DoSomethingByEnum(myEnum); //Pass myEnum
    DoSomethingDynamicByValue(myEnum); //pass myEnum to a dynamic parameter

    dynamic dyn = myEnum;      //assign myenum to a dynamic variable
    DoSomethingDynamicByRef(ref dyn);    //pass it as a reference
}

MyEnum DoSomethingByEnum(MyEnum a)
{
   return a;
}

dynamic DoSomethingDynamicByValue(dynamic inputObject)
{
   return inputObject;

}

dynamic DoSomethingDynamicByRef(ref  dynamic inputObject)
{
  return inputObject;

} 

First we call DoSomethingByEnum passing the variable myEnum by value and then the DoSomethingDynamicByValue passing again myEnum but implicitly boxed as a dynamic type. This is what happens at MSIL level:

Main:
  IL_0001:  ldc.i4.1    // MyEnum myEnum = MyEnum.B;
  IL_0002:  stloc.0     // myEnum popped from evaluation stack and stored in a local variable 
  IL_0003:  ldarg.0     
  IL_0004:  ldloc.0     // myEnum loaded from local variable at index 0 and passed to the function
  IL_0005:  call        DoSomethingByEnum
  IL_000A:  pop         
  IL_000B:  ldarg.0     
  IL_000C:  ldloc.0     // myEnum
  IL_000D:  box         MyEnum // dynamic dyn = myEnum;  
                       // myEnum Converted from value type to a true object reference of type dynamic
  IL_0012:  call        DoSomethingDynamicByValue

The only difference between DoSomethingByEnum(MyEnum) and DoSomethingDynamicByValue(dynamic) is boxing the variable myEnum (accomplished by creating a new object and copying the data from the value type into the newly allocated dynamic object). Check this out Box Opcode

Let's have a look to the DoSomethingByEnum(MyEnum) and DoSomethingDynamicByValue(dynamic) IL:

 DoSomethingDynamicByValue:
    IL_0000:  nop         
    IL_0001:  ldarg.1     
    IL_0002:  stloc.0      
    IL_0003:  br.s        IL_0005
    IL_0005:  ldloc.0      
    IL_0006:  ret         

DoSomethingByEnum:
    IL_0000:  nop         
    IL_0001:  ldarg.1     
    IL_0002:  stloc.0      
    IL_0003:  br.s        IL_0005
    IL_0005:  ldloc.0     
    IL_0006:  ret         

The IL code for both functions is the exact same, regardless of the type of the variable. We could even have a whatever object type but the way the variable is passed and shared across the calls woudln't change.

Let's see instead what happens in the DoSomethingDynamicByRef(ref dynamic).

To continue the main method

Main:
    IL_0018:  ldloc.0     // myEnum
    IL_0019:  box         UserQuery.MyEnum
    IL_001E:  stloc.1     // dyn
    IL_001F:  ldarg.0     
    IL_0020:  ldloca.s    01 // loads the address of dyn onto the stack
    IL_0022:  call        UserQuery.DoSomethingDynamicByRef

DoSomethingDynamicByRef:
   IL_0000:  nop         
   IL_0001:  ldarg.1     
   IL_0002:  ldind.ref   //
   IL_0003:  stloc.0     
   IL_0004:  br.s        IL_0006
   IL_0006:  ldloc.0      
   IL_0007:  ret

The difference between this IL and the last two previous examples relies on these two instructions to load and to fetch an address:

   ldloca.s    01  // loads the address of dyn onto the stack

   ldind.ref       // Loads the object reference at address addr onto the stack as a type O

I think the reasons why it is not possible pass an address of a different object type is explained in the MSDN pages of the two IL instructions above ldloca.s and ldind.ref

Correctly-formed Microsoft Intermediate Language (MSIL) ensures that the ldind instructions are used in a manner consistent with the type of the pointer. The address initially pushed onto the stack must be aligned to the natural size of objects on the machine

hope this can clarify a bit.

Upvotes: 3

Jeffrey Zhao
Jeffrey Zhao

Reputation: 5023

dynamic is actually object after compiling, so you're actually asking why you cannot:

void DoSomething(ref object input);

MyEnum myEnum;
DoSomething(ref myEnum);

The reason is, ref cannot be used like this, please consider how the case below violate the type safety:

void DoSomething(ref object input) {
    input = new object();
}

So as Eric mentioned in the comment, Enum is nothing special here.

Upvotes: 1

Related Questions