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

C#中如何实现可扩展的函数注册?探讨替代手动注册的方案

问题描述

我正在开发一款可通过给定函数名在运行时调用函数的应用,现有手动将函数添加到Dictionary的注册方案可扩展性差,难以在运行时或其他程序集中添加新函数,且重复代码繁琐。我习惯使用Python的@decorators自动注册函数,考虑在C#中为目标函数添加[CustomAttribute],通过遍历程序集自动完成注册,想咨询该方案在设计与性能上是否可行,以及ASP.NET的[HttpGet]采用的实现方式,是否有合适的设计模式可以参考。

现有代码示例:

internal class Caller
{
    public static Dictionary<string, Func<object, object>> reg = new Dictionary<string, Func<object, object>>();
    static Caller()
    {
        reg.Add("example1", Funcs.example1);
        reg.Add("example2", Funcs.example2);
    }
    
    public object Call(string name, object obj)
    {
        return reg[name](obj);
    }
}

internal static class Funcs
{
    public static object example1(object obj)
    {
        // do sth
        return obj;
    }

    public static object example2(object obj)
    {
        // do sth else
        return obj;
    }
}
回答

一、自定义属性自动注册方案的可行性

设计层面

完全可行,这是C#中实现类似Python装饰器自动注册的标准方案,核心逻辑是:

  • 定义自定义标记属性,标记需要自动注册的函数
  • 在应用启动阶段(比如Caller静态构造函数、ASP.NET启动逻辑),遍历指定程序集(当前或外部引用程序集),筛选出带该属性的静态函数
  • 将符合签名要求的函数转换为Func<object, object>并加入注册字典

该方案彻底解决手动注册的痛点:

  • 新增函数只需添加属性标记,无需重复编写reg.Add代码
  • 支持跨程序集注册,只要遍历到目标程序集即可
  • 扩展性强,可通过属性参数配置函数别名、权限等额外信息

性能层面

  • 启动阶段:程序集遍历和反射扫描会增加少量启动时间,但只要不是遍历大量程序集/类型,该开销可忽略(ASP.NET等主流框架均采用类似逻辑)
  • 运行阶段:注册完成后,函数调用逻辑与原方案完全一致,直接从Dictionary取委托执行,无额外性能损耗

二、ASP.NET [HttpGet]的实现逻辑

ASP.NET的[HttpGet]及其他HTTP方法属性,本质也是标记属性+启动时扫描的模式:

  1. 框架启动时(如Program配置阶段),遍历所有控制器类及其中的方法
  2. 识别带有[HttpGet]等属性的方法,解析属性参数(如路由模板)
  3. 将方法信息与路由规则绑定,注册到路由表中
  4. 请求到来时,根据URL和HTTP方法匹配对应控制器方法,通过预编译委托或反射调用

和你要实现的方案核心逻辑一致,区别仅在于ASP.NET绑定的是路由与控制器方法,而你绑定的是函数名与委托。

三、可参考的设计模式

  • 注册模式(Registry Pattern):你的Caller类本身就是注册中心,通过自动扫描强化该模式,让注册过程更自动化
  • 反射驱动的插件模式:若需支持动态加载外部程序集的函数,可结合Assembly.LoadFrom实现插件化扩展,只要插件函数带有自定义属性,就能被自动识别注册
  • 约定优于配置:可约定用方法名作为注册键,或通过属性参数指定别名,减少手动配置成本

四、代码实现示例

1. 定义自定义属性

[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public class FunctionRegisterAttribute : Attribute
{
    // 可选:允许指定自定义函数名,默认使用方法名
    public string? FunctionName { get; set; }

    public FunctionRegisterAttribute() { }
    public FunctionRegisterAttribute(string functionName)
    {
        FunctionName = functionName;
    }
}

2. 修改Caller类实现自动注册

using System.Reflection;

internal class Caller
{
    public static Dictionary<string, Func<object, object>> reg = new Dictionary<string, Func<object, object>>();

    static Caller()
    {
        // 扫描当前程序集,可扩展为扫描多个指定程序集
        var assembly = Assembly.GetExecutingAssembly();
        // 筛选所有带FunctionRegisterAttribute的静态公共方法
        var targetMethods = assembly.GetTypes()
            .SelectMany(type => type.GetMethods(BindingFlags.Public | BindingFlags.Static))
            .Where(method => method.GetCustomAttribute<FunctionRegisterAttribute>() != null);

        foreach (var method in targetMethods)
        {
            // 验证方法签名是否符合要求:单object参数,返回object
            var parameters = method.GetParameters();
            if (parameters.Length != 1 || parameters[0].ParameterType != typeof(object) || method.ReturnType != typeof(object))
            {
                throw new InvalidOperationException($"方法 {method.Name} 签名不符合要求,需匹配 Func<object, object>");
            }

            // 获取注册的函数名(优先使用属性指定的别名,否则用方法名)
            var attr = method.GetCustomAttribute<FunctionRegisterAttribute>()!;
            var registerName = attr.FunctionName ?? method.Name;

            // 将方法转换为委托并注册
            var func = (Func<object, object>)Delegate.CreateDelegate(typeof(Func<object, object>), method);
            reg.Add(registerName, func);
        }
    }

    public object Call(string name, object obj)
    {
        if (!reg.TryGetValue(name, out var targetFunc))
        {
            throw new KeyNotFoundException($"未找到注册的函数:{name}");
        }
        return targetFunc(obj);
    }
}

3. 标记需要注册的函数

internal static class Funcs
{
    [FunctionRegister] // 默认以方法名"example1"注册
    public static object example1(object obj)
    {
        // do sth
        return obj;
    }

    [FunctionRegister("customExample2")] // 指定自定义别名注册
    public static object example2(object obj)
    {
        // do sth else
        return obj;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 23:20:40