在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));

所以你不能简单地用堆栈上的值调用方法,因为它是一个值类型(尽管你可以使用它是一个引用类型)。

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

===============>>#1 票数: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似乎很奇怪。

===============>>#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 票数:1

在对选项进行更多和更深入的考虑之后,我认为你认为不能做到这一点是正确的。 如果检查MSIL指令的堆栈行为,可以看到没有op将其操作数留在堆栈上。 由于这将是“获取堆栈条目的地址”操作的要求,因此我非常有信心不存在。

这留下了dup + box或stloc + ldloca。 正如您所指出的,后者可能更有效率。

@greg:许多指令将结果留在堆栈上,但是没有指令将任何操作数留在堆栈上,这对于“获取堆栈元素地址”指令是必需的。

===============>>#4 票数: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

  ask by Greg Beech translate from so

未解决问题?本站智能推荐:

2回复

如何在C#/ IL中突变盒装值类型(原始或结构)

与如何使用IL突变盒装结构有关,我试图以通用方式更改盒装值类型的值,因此尝试实现以下方法: 因此,以下应该可行: 我无法使它在.NET Framework上正常工作(请参阅@hvd的注释,无需typeof(Program).Module可在其他运行时上运行)。 我已经实现了这一
1回复

使用Reflection.Emit在堆栈上使用MethodInfo实例调用方法

我正在使用Reflection.Emit构建数学表达式解析器(例如2+2 )。 一个类接受一个后缀表达式(例如2+2 ),将其转换为后缀表达式(例如2 2 + ),然后另一个类将该后缀表达式编译为IL并创建DynamicMethod 。 从那里,可以像在编译时创建表达式一样以类似的速度对其进
1回复

Emit属性设置字典值

我尝试为动态程序集中的属性发出set字段方法。 想要的C#代码是: PropertyName及其类型必须是动态的 我发出setter的代码是: 当运行设置属性的代码时,我有一个 AccessViolationException :尝试读取或写入受保护的内存。
1回复

在相同类型的ILGenerator上调用动态生成的方法

通常,当我想在另一个以相同类型编写方法的ILGenerator对象中调用动态方法时,请执行以下操作: 但是,我遇到了以下问题:我无法引用要调用的方法的methodbuilder,因为它是由另一个框架生成的(我仅获得对当前TypeBuilder的引用)。 在当前动态类型的基类中,此方法被
1回复

动态类型的Msil Emit静态数组

我正在尝试使用Reflection.Emit(在c#中)创建一个新类型。 我想要创建的代码类似于 我首先尝试定义一个字段,然后设置其值: 但它不起作用,因为setValue仅支持简单类型(int,float,...)。 现在我正在尝试使用DefineInitializ
1回复

IL Emit-在notifypropertychanged更改之前,将现有属性设置为布尔值

我正在为包含虚拟自动属性的POCO对象实现一个发出的propertychanged处理程序,并且我的代码可以工作到每当我更改基础属性时引发propertychanged的地步。 这样做的原因是,我正在与服务器共享POCO对象(无论好坏),在那里我将修改后的对象发送到服务器。 我不能用属性修饰
1回复

如何通过调用其构造函数来发出代码以将值/引用分配给类的静态字段?

(我的代码有点C#和VB.NET的混乱)我正在尝试发出如下所示的类: 但是,我正在为两个静态成员分配引用。 到目前为止,我已经明白了这一点(下面的代码在VB.NET中,但是我很乐意接受VB.NET或C#中的答案):
1回复

阅读DynamicMethod的LocalSignature:非标准类型的令牌吗?

我最近开始在VS2010中使用ILVisualizer来检查IL的动态方法。 但是,它不会从IL流中提取LocalVariable信息,因此,我尝试一下(当然,这样做很有趣,因为这是一种很好的学习体验:) 对于静态编译的方法,这不是问题,因为我们可以通过GetMethodBody()访问
2回复

创建一个DynamicMethod来设置任何类型的只读字段

我的目标是在运行时创建一个委托,该委托可以将任何引用类型中的任何字段(包括readonly )设置为用户指定的值。 不幸的是,当包含类型的程序集指定[AllowPartiallyTrustedCallers]属性时,我当前的实现在运行时引发VerificationException 。
1回复

ILGenerator Emit:加载propertyInfo具有方法参数

我正在尝试使用ILGenerator.Emit在IL中创建此代码 这是我到目前为止所做的,并且有一些复杂之处: 它给了我以下异常:-> 预期类型:System.Type,接收到的类型:System.String System.String是由pi.PropertyTy