如何在C#/IL中变异装箱值类型(原语或结构)
与之相关,我正在尝试更改装箱值类型的值,但是以通用方式,因此尝试实现以下方法:如何在C#/IL中变异装箱值类型(原语或结构),c#,.net,cil,reflection.emit,C#,.net,Cil,Reflection.emit,与之相关,我正在尝试更改装箱值类型的值,但是以通用方式,因此尝试实现以下方法: void MutateValueType<T>(object o, T v) where T : struct 以下是我提出的实施方案: public static void MutateValueType<T>(object o, T v) { var dynMtd = new DynamicMethod("EvilMutateValueType", typeof
void MutateValueType<T>(object o, T v) where T : struct
以下是我提出的实施方案:
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);
}
publicstaticvoid MutateValueType(对象o,tv)
{
var dynMtd=新的DynamicMethod(“EvillumateValueType”,
typeof(void),新类型[]{typeof(object),typeof(T)};
var il=dynMtd.GetILGenerator();
il.Emit(操作码.Ldarg_0);//对象
il.Emit(opcode.Unbox,typeof(T));//T&
il.Emit(操作码.Ldarg_1);//T(参数值)
Emit(opcode.Stobj,typeof(T));//Stobj!!T
发射(操作码Ret);
var del=(Action)dynMtd.CreateDelegate(typeof(Action));
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
}
.method public hidebysing static void Mutate(对象o,!!T值)cil管理的攻击性内联
{
.maxstack 2
ldarg.0
拆箱!!T
ldarg.1
stobj!!T
ret
}
一种解决方案是在IL中创建一个Unbox
方法,该方法调用Unbox
,并将ref
返回到对象中包含的类型:
.method public hidebysig static !!T& Unbox<T>(object o) cil managed aggressiveinlining
{
.maxstack 1
ldarg.0
unbox !!T
ret
}
.method公共隐藏静态!!T&Unbox(对象o)cil管理的攻击性联机
{
.maxstack 1
ldarg.0
拆箱!!T
ret
}
然后像这样使用:
public static void MutateValueType<T>(object o, T v)
{
ref T ub = ref Unsafe.Unbox<T>(o);
ub = v;
}
publicstaticvoid MutateValueType(对象o,tv)
{
ref T ub=ref Unbox(o);
ub=v;
}
这将正确地输出:
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
var oi=(object)17;
突变型(oi,43);
控制台写入线(oi);//43
var od=(对象)17.7d;
变异价值型(od,42.3);
控制台写入线(od);//42.3
注意:这需要C#7支持ref
返回
这可能是为了说,但是它也必须可以与
il.Emit
一起使用,所以我正在寻找它。不同的是DynamicMethod
默认情况下需要可验证的代码,而您自己的代码(包括自定义il)默认情况下是不可验证的
通过指定模块,您可以将DynamicMethod
视为自己模块的一部分,允许它包含无法验证的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中存在一些其他问题,使其难以获得良好的诊断,但看起来这至少是不可验证的:
III.1.8.1.2.2受控可变性管理指针
readonly.
前缀和unbox
指令可以生成所谓的受控可变管理指针。与普通托管指针类型不同,受控可变托管指针不是可分配给(§III.1.8.1.2.3)普通托管指针的验证器;e、 不可能
作为byref参数传递给方法。在控制流点,受控可变性托管指针可以与相同类型的托管指针合并,以生成受控可变性托管指针
受控可变性托管指针只能以以下方式使用:
ldfld
、ldflda
、stfld
、call
、callvirt
或约束的对象参数。callvirt
指令ldind.*
或ldobj
指令的指针参数cpobj
指令的源参数stobj
、stind.*
、initobj
和mkrefany
)均无效
[……]
但它看起来仍然是正确的:
III.4.29 stobj–将值存储在地址上
[……]
正确性:
正确的CIL确保dest是指向T
的指针,并且src的类型是可分配给T
的验证器
[……]
注意,这里对受控可变性托管指针没有限制,允许任何指向T
的指针
因此,确保不验证IL是正确的方法。我不确定是否可以完全替换框中的内容,也不确定为什么您认为应该这样做(至少是以通用方式)。因为
unbox
只是返回一个指向该值的托管指针,或者换句话说,包含值类型的内存地址。应该可以为此分配一个新值。这应该对应于一个返回<代码> REF <代码>的方法,例如<代码> REF unbox(object o)< /> >,事实上,我认为这可以在IL实现,然后用于上述。@ DaMiNeNeth-Tun-Unistiver考虑了一个简单的<代码>结构>邪恶{int i;公共覆盖字符串toString(){{+i;返回i toString();}}//>然后objecto=newevil()代码>。现在,规范要求o.ToString()!=o、 ToString()
——也就是说,规范要求运行时支持装箱值的就地更新。鉴于无论如何都需要运行时来支持这一点,并且鉴于我的示例没有使用任何实际需要相关值类型合作的内容,我似乎很清楚OP后面的内容应该是可能的。@hvd-规范要求结构的成员是可更新的。它不要求整个结构都是可重放的。也就是说,它不要求struct
构造函数中可能的this
赋值一般可用。@Damien_不相信我的例子,我可以很容易地把this=new-Evil{I=I+1}
修改整个结构。结构赋值从未局限于构造函数。这很有效。非常感谢你。如果你愿意的话,也许你可以在最后添加一个注释,并附上最终的完整代码
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
var dynMtd = new DynamicMethod("EvilMutateValueType",
typeof(void), new Type[] { typeof(object), typeof(T) }, typeof(Program).Module);
// Use whatever class you have available here. ^^^^^^^^^^^^^^^^^^^^^^