You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);

关键说明

  1. 参数修饰符的指定:在EmitCall的第三个参数中,我们传入修饰符数组,标记第二个参数带有IsReadOnly修饰,让CLR能正确识别参数类型,匹配目标方法的签名。
  2. IL指令的正确性:Ldarg_1依然可以正常使用,因为动态方法的Context&参数,通过modreq可以被CLR正确识别为目标方法需要的readonly Context&。
  3. 调用代码兼容性:原调用代码无需改动,C#编译器会自动将值类型变量tm包装为readonly引用传递给Invoke方法。

内容的提问来源于stack exchange,提问作者zgabi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 17:42:47