作为引用传递的唯一方法是将 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 进行装箱(通过创建新对象并将值类型中的数据复制到新分配的动态对象中来完成)。检查此框操作码
让我们看一下 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.s和ldind.ref上面的两条 IL 指令的 MSDN 页面中进行了解释
格式正确的 Microsoft 中间语言 (MSIL) 可确保以与指针类型一致的方式使用 ldind 指令。最初压入堆栈的地址必须与机器上对象的自然大小对齐
希望这可以澄清一点。