C#中创建带in参数的动态方法时EmitCall抛出异常的问题排查
解决.NET动态方法调用带
in参数方法时EmitCall抛出异常的问题 我来帮你分析问题根源,以及对应的修复方案:
首先你遇到的核心问题是**in参数在IL层面的特殊处理逻辑**——C#里的in修饰参数本质是readonly ref,在IL字节码中会带有[modreq(System.Runtime.CompilerServices.IsReadOnly)]的修饰符。你的动态方法代码没有正确声明这个修饰符,导致CLR无法匹配目标方法的签名,最终抛出异常。
错误原因拆解
- 当你在C#中写
in Context参数时,编译器会将其编译为带有IsReadOnly修饰的ref Context(即readonly Context&)。 - 你的动态方法虽然用
MakeByRefType()创建了Context&类型的参数,但在调用目标方法时,没有告诉CLR这个参数需要被当作readonly引用处理,导致方法签名不匹配。
修复后的完整代码
下面是修正后的代码,关键改动已标注:
public struct Context { public string MyString { get; set; } } public interface IHandler<T> { void MyMethod(in Context context); } class MyMessage { } class MyClass : IHandler<MyMessage> { public void MyMethod(in Context context) { } } class ContextMethodInvoker { private readonly InvokeHandler _invoker; private delegate void InvokeHandler(object handler, in Context context); public ContextMethodInvoker(MethodInfo methodInfo) { var dynamicMethod = new DynamicMethod(string.Empty, typeof(void), new[] { typeof(object), typeof(Context).MakeByRefType() }); dynamicMethod.DefineParameter(1, ParameterAttributes.None, "handler"); dynamicMethod.DefineParameter(2, ParameterAttributes.In, "context"); var il = dynamicMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // 加载handler实例 il.Emit(OpCodes.Ldarg_1); // 加载in Context的引用 // 关键修复:获取IsReadOnly修饰符,并在EmitCall时指定参数修饰符 var isReadOnlyModReq = typeof(System.Runtime.CompilerServices.IsReadOnly); // 第一个参数(handler)无修饰符,第二个参数带有IsReadOnly modreq var parameterModifiers = new[] { null, new[] { isReadOnlyModReq } }; il.EmitCall(OpCodes.Callvirt, methodInfo, parameterModifiers); il.Emit(OpCodes.Ret); _invoker = (InvokeHandler)dynamicMethod.CreateDelegate(typeof(InvokeHandler)); } public void Invoke(object handler, in Context context) { _invoker.Invoke(handler, in context); } } // 调用代码无需改动,编译器会自动处理in参数的传递 var messageHandlerType = typeof(IHandler<>); var messageType = typeof(MyMessage); var type = typeof(MyClass); var interfaceType = messageHandlerType.MakeGenericType(messageType); var contextMethod = new ContextMethodInvoker( type.GetInterfaceMap(interfaceType).TargetMethods.SingleOrDefault() ); var tm = new Context { MyString = "test" }; contextMethod.Invoke(Activator.CreateInstance(type), tm);
关键说明
- 参数修饰符的指定:在
EmitCall的第三个参数中,我们传入修饰符数组,标记第二个参数带有IsReadOnly修饰,让CLR能正确识别参数类型,匹配目标方法的签名。 - IL指令的正确性:
Ldarg_1依然可以正常使用,因为动态方法的Context&参数,通过modreq可以被CLR正确识别为目标方法需要的readonly Context&。 - 调用代码兼容性:原调用代码无需改动,C#编译器会自动将值类型变量
tm包装为readonly引用传递给Invoke方法。
内容的提问来源于stack exchange,提问作者zgabi
相关产品推荐
相关产品推荐

