【问题标题】:Cannot convert ref enum to ref dynamic?无法将引用枚举转换为引用动态?
【发布时间】:2013-03-23 00:54:18
【问题描述】:

给定一个函数声明

dynamic DoSomething(dynamic inputObject)

我可以用inputObject 的枚举来调用它:

MyEnum myEnum;
myEnum = DoSomething(myEnum);

但由于某种原因,如果函数将 inputObject 声明为类型 ref dynamic 而不是 dynamic

dynamic DoSomething(ref dynamic inputObject)

由于转换无效,以下操作无效:

MyEnum myEnum;
DoSomething(ref myEnum);

枚举是否有什么特别之处阻止我将它们与ref dynamic 一起使用?

【问题讨论】:

  • 可以this 相关吗?
  • 好吧,如果您想知道枚举是否特殊,您可以尝试使用非枚举类型,例如 int、string 或 object。试试这些,看看会发生什么。你能弄清楚模式吗?一旦确定了枚举是否特殊,请考虑使用 ref 变量可以做的所有事情。有什么方法可以违反类型安全?

标签: c# c#-4.0 dynamic enums


【解决方案1】:

作为引用传递的唯一方法是将 myEnum 转换为动态类型,然后通过引用传递它。我认为我们应该仔细研究生成的 IL,以了解幕后发生的事情。让我们找出原因并分析这个程序:

 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;

} 

首先我们调用 DoSomethingByEnum 按值传递变量 myEnum,然后调用 DoSomethingDynamicByValue 再次传递 myEnum 但隐式装箱为动态类型。这是在 MSIL 级别发生的情况:

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

DoSomethingByEnum(MyEnum) 和 DoSomethingDynamicByValue(dynamic) 之间的唯一区别是对变量 myEnum 进行装箱(通过创建一个新对象并将值类型中的数据复制到新分配的动态对象中来实现)。看看这个Box Opcode

让我们看看 DoSomethingByEnum(MyEnum) 和 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         

无论变量的类型如何,这两个函数的 IL 代码都是完全相同的。我们甚至可以有任何对象类型,但变量在调用之间传递和共享的方式不会改变。

让我们看看 DoSomethingDynamicByRef(ref dynamic) 中发生了什么。

继续主方法

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

这个 IL 和前面两个例子的区别在于这两个指令来加载和获取地址:

   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

我认为无法传递不同对象类型的地址的原因在ldloca.sldind.ref上面两条IL指令的MSDN页面中进行了解释

格式正确的 Microsoft 中间语言 (MSIL) 可确保 ldind 指令的使用方式与类型一致 的指针。最初压入堆栈的地址必须是 与机器上物体的自然尺寸对齐

希望这可以澄清一点。

【讨论】:

  • 谢谢,这是一个很棒的答案。
【解决方案2】:

dynamic在编译后实际上是object,所以你实际上是在问为什么你不能:

void DoSomething(ref object input);

MyEnum myEnum;
DoSomething(ref myEnum);

原因是,ref 不能这样使用,请考虑以下情况如何违反类型安全:

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

正如 Eric 在评论中提到的,Enum 在这里没什么特别的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-26
    • 1970-01-01
    • 2012-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-21
    相关资源
    最近更新 更多