Gre*_*ech 7 cil reflection.emit
在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);
Run Code Online (Sandbox Code Playgroud)
但是,跳过将其保存为局部变量并简单地在堆栈上已有的变量的地址上调用方法会很好,例如:
il.Emit(/* not sure */);
var method = typeof(Nullable<int>).GetMethod("get_HasValue");
il.EmitCall(OpCodes.Call, method, null);
Run Code Online (Sandbox Code Playgroud)
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));
Run Code Online (Sandbox Code Playgroud)
所以你不能简单地用堆栈上的值调用方法,因为它是一个值类型(尽管你可以使用它是一个引用类型).
我想要实现(或知道是否可能)是替换显示注释的三行,但保持程序正常工作,而不使用临时本地.
如果变量已经在堆栈中,您可以继续并发出方法调用。
看来构造函数并没有以类型化的形式将变量压入堆栈。深入研究一下 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));
Run Code Online (Sandbox Code Playgroud)
另一种选择是按照您展示的方式进行。我能看到的唯一原因是 ctor 方法返回 void,因此它们不会像其他方法一样将其值放在堆栈上。如果新对象不在堆栈上,您可以调用 Setloc,这看起来确实很奇怪。