使用DynamicMethod调用值类型方法(如Int64.ToString())的问题
解决DynamicMethod调用值类型实例方法的问题
这个问题的核心在于值类型装箱后的处理逻辑缺失:当你把值类型(比如Int64)传入FastInvokeHandler的target参数时,值类型会被自动装箱为object。但你的动态IL代码里直接使用了Ldarg_0(装箱后的对象引用)来调用值类型的实例方法,而值类型的实例方法需要操作的是值本身,不是装箱后的引用,这就导致方法实际被调用在了装箱对象的指针上,返回了错误的结果。
修复方案
在生成IL代码时,针对非静态方法,判断目标类型是否为值类型,如果是,需要先对target参数执行解箱操作,拿到值类型的实例地址后再调用方法。
修改GetMethodInvoker方法中处理非静态方法实例的代码段:
原来的代码:
if (!methodInfo.IsStatic) { il.Emit(OpCodes.Ldarg_0); }
修改为:
if (!methodInfo.IsStatic) { il.Emit(OpCodes.Ldarg_0); // 针对值类型,解箱获取其实例地址 if (methodInfo.DeclaringType.IsValueType) { il.Emit(OpCodes.Unbox, methodInfo.DeclaringType); } }
为什么这样能解决问题?
- 对于引用类型(比如
Person),Ldarg_0直接拿到对象引用,Callvirt可以正常调用实例方法,和原来的逻辑一致。 - 对于值类型,装箱后的
object是一个包含值类型实例的包装对象,Unbox指令会获取到值类型实例的内存地址,值类型的实例方法正是通过这个地址来操作值本身的,这样ToString()就会正确作用在Int64的值上,而不是装箱对象的指针。
修改后的测试效果
当你运行TestInvokeToString时,控制台会输出:
1 converted is 1
完全符合预期。
补充说明
如果你的代码需要支持更多值类型的复杂方法(比如带ref/out参数的),当前的本地变量处理逻辑已经能应对,因为你已经在处理参数时做了Unbox_Any的转换,这次修改只是补上了目标实例的解箱逻辑。
内容的提问来源于stack exchange,提问作者raff
相关产品推荐
相关产品推荐

