在Microsoft IL中,要在值类型上调用方法,您需要间接引用。假设我们有一个名为“il”的ILGenerator,目前我们在堆栈顶部有一个Nullable,如果我们想检查它是否有值,那么我们可以发出以下内容:
var local = il.DeclareLocal(typeof(Nullable<int>));
il.Emit(OpCodes.Stloc, local);
il.Emit(OpCodes.Ldloca, local);
var method = typeof(Nullable<int>).GetMethod("get_HasValue");
il.EmitCall(OpCodes.Call, method, null);
然而,将它保存为局部变量并简单地在堆栈上已有的变量的地址上调用方法会很好,例如:
il.Emit(/* not sure */);
var method = typeof(Nullable<int>).GetMethod("get_HasValue");
il.EmitCall(OpCodes.Call, method, null);
ldind系列指令看起来很有前途(特别是ldind_ref),但我找不到足够的文档来知道这是否会导致值的装箱,我怀疑它可能。
我已经看过C#编译器输出,但它使用局部变量来实现这一点,这让我相信第一种方式可能是唯一的方法。谁有更好的想法?
****编辑:附加说明****
尝试直接调用该方法,如下面的程序中注释掉的行一样,不起作用(错误将是“操作可能使运行时不稳定”)。取消注释行,您将看到它按预期工作,返回“True”。
var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
var il = m.GetILGenerator();
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) });
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Newobj, ctor);
//var local = il.DeclareLocal(typeof(Nullable<int>));
//il.Emit(OpCodes.Stloc, local);
//il.Emit(OpCodes.Ldloca, local);
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue");
il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(m.Invoke(null, null));
所以你不能简单地用堆栈上的值调用方法,因为它是一个值类型(尽管你可以是引用类型)。
我想要实现(或知道是否可能)是替换显示的三行,但保持程序正常工作,而不使用临时本地。
答案 0 :(得分:2)
如果变量已经在堆栈中,您可以继续发出方法调用。
似乎构造函数不会以类型形式推送堆栈上的变量。在深入挖掘IL之后,似乎有两种方法在构造它之后使用变量。
您可以在调用构造函数之前加载将引用存储到评估堆栈的变量,然后在调用构造函数之后再次加载该变量:
DynamicMethod method = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
ILGenerator il = method.GetILGenerator();
Type nullable = typeof(Nullable<int>);
ConstructorInfo ctor = nullable.GetConstructor(new Type[] { typeof(int) });
MethodInfo getValue = nullable.GetProperty("HasValue").GetGetMethod();
LocalBuilder value = il.DeclareLocal(nullable);
// load the variable to assign the value from the ctor to
il.Emit(OpCodes.Ldloca_S, value);
// load constructor args
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Call, ctor);
il.Emit(OpCodes.Ldloca_S, value);
il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(method.Invoke(null, null));
另一种选择就是按照你所展示的方式进行。我能看到的唯一原因是ctor方法返回void,因此它们不像其他方法那样将它们的值放在堆栈上。如果新对象不在堆栈中,您可以调用Setloc似乎很奇怪。
答案 1 :(得分:1)
在对选项进行更多和更深入的考虑之后,我认为你认为你无法做到这一点是正确的。如果检查MSIL指令的堆栈行为,可以看到没有op将其操作数留在堆栈上。由于这是“获取堆栈条目的地址”操作的要求,因此我非常有信心不存在。
这会留下dup + box或stloc + ldloca。正如你所指出的,后者可能更有效率。
@greg:许多指令将结果留在堆栈上,但没有指令将任何操作数留在堆栈上,这对于'get stack是必需的元素地址'指令。
答案 2 :(得分:1)
我明白了!幸运的是,我正在阅读unbox
操作码,并注意到它推动了值的地址。 unbox.any
推动实际价值。因此,为了在值类型上调用方法而不必将其存储在局部变量中然后加载其地址,您只需box
后跟unbox
。使用你的上一个例子:
var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
var il = m.GetILGenerator();
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) });
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Box, typeof(Nullable<int>)); // box followed by unbox
il.Emit(OpCodes.Unbox, typeof(Nullable<int>));
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue");
il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(m.Invoke(null, null));
这样做的缺点是装箱导致盒装对象的内存分配,因此它比使用局部变量(已经分配)慢一点。但是,它使您无需确定,声明和引用所需的所有局部变量。
答案 3 :(得分:0)
刚写了一个做OP所要求的类......这里是C#编译器产生的IL代码:
IL_0008: ldarg.0
IL_0009: ldarg.1
IL_000a: newobj instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
IL_000f: stfld valuetype [mscorlib]System.Nullable`1<int32> ConsoleApplication3.Temptress::_X
IL_0014: nop
IL_0015: ret