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

如何声明与指定方法签名匹配的委托类型,避免重复维护?

这个需求太接地气了——谁都不想因为API签名改了,还要同时改原生接口、托管实现、委托声明三处,简直是维护噩梦!好在.NET里确实有几种办法能帮你摆脱手动声明委托的麻烦,下面给你逐个拆解:

方案1:用Delegate.CreateDelegate动态创建委托(无需提前声明委托类型)

你完全不用手动写APIDel这种委托类型,直接从目标方法动态生成匹配的委托就行。比如你的例子可以改成这样:

class Foo { public void APIFunction() { /* 你的API实现 */ } }

IntPtr GetAPIFunctionPointer()
{
    var foo = new Foo();
    // 根据方法签名选择对应的预定义委托(无参无返回值用Action,带参数/返回值用Func<T>系列)
    Action del = (Action)Delegate.CreateDelegate(typeof(Action), foo, nameof(Foo.APIFunction));
    IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del);
    
    // 关键提醒:必须保留委托的引用!如果del是局部变量,GC可能会回收它,导致指针失效
    // 最好把委托存在类的字段里(比如一个List<Delegate>),而不是方法内的临时变量
    GC.KeepAlive(del); 
    return ptr;
}

如果你的API方法带参数或返回值,只需要换成对应的Func泛型委托就行——比如int APIFunc(string arg)就用Func<string, int>作为委托类型。

方案2:C# 9+ 函数指针语法(直接跳过委托,获取原生指针)

如果你用的是C# 9及以上版本,可以直接用函数指针语法,连委托都不用创建,直接拿到原生可调用的指针,效率更高:

class Foo { public void APIFunction() { /* 你的API实现 */ } }

// 需要开启unsafe编译选项(项目属性→构建→允许不安全代码)
unsafe IntPtr GetAPIFunctionPointer()
{
    var foo = new Foo();
    // 直接获取实例方法的函数指针,签名匹配就用对应的delegate*<>类型
    // 无参无返回值用delegate*<void>,如果是int参数+string返回值就是delegate*<int, string>
    delegate*<void> funcPtr = &foo.APIFunction;
    return (IntPtr)funcPtr;
}

这个方式省去了委托的包装,直接和原生指针对接,不过需要注意开启unsafe模式,而且静态方法的话更简单,直接&Foo.APIFunction就行。

方案3:源生成器(批量自动生成委托,适合大规模API)

如果你的插件API有一堆方法,手动用上面的方案还是有点麻烦,可以用.NET的源生成器自动生成所有匹配的委托类型。

比如你有一个镜像C API的托管接口:

public interface IPluginAPI
{
    void APIFunction();
    int AnotherAPIFunc(string param);
}

写一个简单的源生成器,遍历这个接口的所有方法,自动生成对应的委托类型:

// 源生成器自动输出的代码(无需手动写)
public delegate void APIFunctionDelegate();
public delegate int AnotherAPIFuncDelegate(string param);

这样每次你修改接口的方法签名,源生成器会自动同步更新委托类型,彻底消除手动维护的成本。

针对你的插件场景的适配建议

结合你托管+非托管插件的场景,最优的做法是遍历托管接口的所有方法,动态创建委托并封送指针,同时把委托实例存起来防止GC回收:

// 类内的字段,用来保存所有注册的委托,防止被GC回收
private List<Delegate> _registeredDelegates = new();

public void RegisterPluginAPI(IPluginAPI pluginInstance)
{
    var apiMethods = typeof(IPluginAPI).GetMethods();
    foreach (var method in apiMethods)
    {
        // 根据方法签名自动匹配对应的委托类型
        Type delegateType = GetMatchingDelegateType(method);
        Delegate apiDelegate = Delegate.CreateDelegate(delegateType, pluginInstance, method);
        
        // 封送为原生函数指针,传给原生侧
        IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(apiDelegate);
        NativePluginLoader.RegisterFunction(method.Name, funcPtr);
        
        // 保存委托引用,避免GC回收
        _registeredDelegates.Add(apiDelegate);
    }
}

// 辅助方法:根据MethodInfo生成对应的Action/Func委托类型
private Type GetMatchingDelegateType(MethodInfo method)
{
    var paramTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
    if (method.ReturnType == typeof(void))
    {
        return paramTypes.Length == 0 ? typeof(Action) : typeof(Action<>).MakeGenericType(paramTypes);
    }
    else
    {
        var allTypes = paramTypes.Concat(new[] { method.ReturnType }).ToArray();
        return typeof(Func<>).MakeGenericType(allTypes);
    }
}

这样不管你的API接口怎么改,这段代码都不需要手动调整,完美解决“三处同步更新”的痛点。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:07:22