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

与如何使用IL改变盒装结构相关我试图以通用方式更改盒装值类型的值,因此尝试实现以下方法:

void MutateValueType(object o, T v) where T : struct 

所以以下应该是可能的:

 var oi = (object)17; MutateValueType(oi, 43); Console.WriteLine(oi); // 43 var od = (object)17.7d; MutateValueType(od, 42.3); Console.WriteLine(od); // 42.3 

我没有得到它在.NET Framework上工作(请参阅@hvd的评论,没有typeof(Program).Module适用于其他运行时)。 我已经实现了这个,如下所示。 但是,在使用以下命令调用委托del时,这会失败:

 System.Security.VerificationException: 'Operation could destabilize the runtime.' 

这是我提出的实现:

 public static void MutateValueType(object o, T v) { var dynMtd = new DynamicMethod("EvilMutateValueType", typeof(void), new Type[] { typeof(object), typeof(T) }); var il = dynMtd.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // object il.Emit(OpCodes.Unbox, typeof(T)); // T& il.Emit(OpCodes.Ldarg_1); // T (argument value) il.Emit(OpCodes.Stobj, typeof(T)); // stobj !!T il.Emit(OpCodes.Ret); var del = (Action)dynMtd.CreateDelegate(typeof(Action)); del(o, v); } 

以上应该等同于下面的IL,这有效,但仍然上面的失败,所以问题是为什么这不起作用。

  .method public hidebysig static void Mutate(object o, !!T Value) cil managed aggressiveinlining { .maxstack 2 ldarg.0 unbox !!T ldarg.1 stobj !!T ret } 

不同之处在于,默认情况下DynamicMethod需要可validation的代码,而默认情况下,您自己的代码(包括自定义IL)是无法validation的。

您可以将DynamicMethod视为您自己模块的一部分,通过指定模块允许它包含无法validation的IL:

 var dynMtd = new DynamicMethod("EvilMutateValueType", typeof(void), new Type[] { typeof(object), typeof(T) }, typeof(Program).Module); // Use whatever class you have available here. ^^^^^^^^^^^^^^^^^^^^^^ 

尽管PEVerify中的一些其他问题使得很难获得良好的诊断,但看起来这至少是不可validation的:

III.1.8.1.2.2受控可变性管理指针

readonly. 前缀和unbox指令可以产生所谓的受控可变性管理指针 。 与普通托管指针类型不同,受控可变性管理指针不是validation者可分配给 (§III.1.8.1.2.3)普通托管指针; 例如,它不能作为byref参数传递给方法。 在控制流点处,受控可变性管理指针可以与相同类型的受管指针合并,以产生受控可变性管理指针。

受控可变性管理指针只能以下列方式使用:

  1. 作为ldfldldfldastfldcallcallvirtconstrained. callvirt的对象参数constrained. callvirt constrained. callvirt指令。
  2. 作为ldind.*ldobj指令的指针参数。
  3. 作为cpobj指令的源参数。

所有其他操作(包括stobjstind.*initobjmkrefany )都无效。

[…]

但看起来它仍然是正确的:

III.4.29 stobj – 将值存储在地址中

[…]

正确性:

正确的CIL确保dest是指向T的指针, src的类型是verifier-assignable-to T

[…]

请注意,此处对受控可变性管理指针没有限制,允许任何指向T指针。

因此,确保您的IL无法进行validation是正确的方法。

一种解决方案是在IL中创建一个调用unboxUnbox方法,并将ref返回给对象中包含的类型:

 .method public hidebysig static !!T& Unbox(object o) cil managed aggressiveinlining { .maxstack 1 ldarg.0 unbox !!T ret } 

然后使用这个:

 public static void MutateValueType(object o, T v) { ref T ub = ref Unsafe.Unbox(o); ub = v; } 

这正确输出:

  var oi = (object)17; MutateValueType(oi, 43); Console.WriteLine(oi); // 43 var od = (object)17.7d; MutateValueType(od, 42.3); Console.WriteLine(od); // 42.3 

注意:这需要C#7支持ref返回。

这可能会添加到https://github.com/DotNetCross/Memory.Unsafe,但也必须使用il.Emit ,所以我正在寻找。