【发布时间】:2017-06-23 14:35:46
【问题描述】:
与How to mutate a boxed struct using IL相关我正在尝试以通用方式更改装箱值类型的值,因此尝试实现以下方法:
void MutateValueType<T>(object o, T v) where T : struct
所以以下应该是可能的:
var oi = (object)17;
MutateValueType<int>(oi, 43);
Console.WriteLine(oi); // 43
var od = (object)17.7d;
MutateValueType<double>(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<T>(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<object, T>)dynMtd.CreateDelegate(typeof(Action<object, T>));
del(o, v);
}
上面应该等效于下面的IL,可以工作,但上面仍然失败,所以问题是为什么这不起作用。
.method public hidebysig static void Mutate<T>(object o, !!T Value) cil managed aggressiveinlining
{
.maxstack 2
ldarg.0
unbox !!T
ldarg.1
stobj !!T
ret
}
【问题讨论】:
-
我不确定它是否可以完全替换盒子的内容,也不确定你为什么认为它应该是(至少,以一般的方式)。
-
因为
unbox只是返回一个指向值的托管指针,或者换句话说,包含值类型的内存地址。应该可以为此分配一个新值。这应该对应于返回ref的方法,例如ref T Unbox<T>(object o),其实我觉得这可以在IL中实现,然后习惯上面的。 -
@Damien_The_Unbeliever 考虑一个简单的
struct Evil { int i; public override string ToString() { ++i; return i.ToString(); } },然后是object o = new Evil();。现在,规范需要o.ToString() != o.ToString()——也就是说,规范需要运行时支持盒装值的就地更新。鉴于无论如何都需要运行时来支持这一点,并且鉴于我的示例没有使用任何实际需要与所讨论的值类型合作的东西,我似乎很清楚 OP 所追求的应该是可能的. -
@hvd - 规范要求结构的 成员 是可更新的。它不要求 entire 结构是可替换的。 IE。它不需要
struct构造函数中可能的this赋值普遍可用。 -
@Damien_The_Unbeliever 在我的示例中,我可以很容易地使用
this = new Evil { i = i + 1 };来修改整个结构。结构赋值从未局限于构造函数。
标签: c# .net cil reflection.emit