2013-03-23 65 views
3

给定函数声明无法将ref enum转换为ref动态?

dynamic DoSomething(dynamic inputObject) 

我可以用一个枚举称其为inputObject

MyEnum myEnum; 
myEnum = DoSomething(myEnum); 

但由于某些原因,如果函数要宣布inputObjectref dynamic类型,而不是dynamic

dynamic DoSomething(ref dynamic inputObject) 

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

MyEnum myEnum; 
DoSomething(ref myEnum); 

有大约枚举一些特别的东西使我不能用他们ref dynamic

+0

可能[this](http://stackoverflow.com/q/2475310/335858)有关系吗? – dasblinkenlight 2013-03-23 01:02:07

+5

好吧,如果你想知道枚举是否特殊,你可以用非枚举类型来尝试它,比如int,string或object。试试看看会发生什么。你能弄清楚这种模式吗?一旦你找出枚举是否特殊,考虑你可以用ref变量做的所有事情。有没有什么方法可以违反类型安全? – 2013-03-23 02:40:58

回答

3

作为参考传递的唯一方法是将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(动态)是拳击可变myEnum(通过从值类型创建一个新对象并复制数据到完成之间的唯一区别新分配的动态对象)。检查了这一点Box Opcode

让我们看看到DoSomethingByEnum(MyEnum)和DoSomethingDynamicByValue(动态)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动态)中会发生什么。

要继续的主要方法

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 

我觉得在两个IL指令的MSDN页面上面解释了为什么不可能传递不同对象类型的地址的原因:ldloca.sldind.ref

正确形成的微软中间语言(MSIL)确保lind指令的使用方式与指针的类型 一致。地址最初压入堆栈必须 对准对象的自然尺寸在机器上

希望这可以澄清一点。

+0

谢谢,这是一个很棒的答案。 – 2013-03-25 01:21:21

1

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

void DoSomething(ref object input); 

MyEnum myEnum; 
DoSomething(ref myEnum); 

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

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

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