cil - 是否可以在堆栈上间接加载值类型

标签 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);

但是,最好跳过将其保存为局部变量,而只需在堆栈中已有的变量地址上调用该方法,例如:
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));

因此,您不能简单地使用堆栈上的值调用该方法,因为它是值类型(尽管如果它是引用类型,则可以)。

我想要实现(或知道是否可能)是替换显示的三行注释掉,但保持程序正常工作,而不使用临时本地。

最佳答案

如果变量已经在堆栈中,您可以继续并只发出方法调用。

似乎构造函数没有以类型化的形式将变量压入堆栈。在深入研究 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 似乎很奇怪。

关于cil - 是否可以在堆栈上间接加载值类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76274/

相关文章:

c# - 尝试创建新类型时出现 InvalidProgramException

c# - 如何为 ulong.Parse 调用发出 LDC_I8?

llvm - MSIL 和 LLVM 位码有什么区别?

c# - 如何从 TypeSpec 获取 TypeDef

c# - 编译器为 delegate 关键字生成的密封类包含虚方法

c# - 什么更快 : expression trees or manually emitting IL

c# - 当我将一个类声明为内部类时,为什么 IL 将其显示为私有(private)类?

c# - 发出属性以设置字典值

c# - 注入(inject)从 MethodInfo.GetMethodBody() 导出的 byte[]

.net - 通过 Reflection.Emit 调用默认的无参数值类型构造函数