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

如何编写可维护的编译时List<Action<T>>,支持元素间互相调用

嘿,这个需求太常见了——既要把每个Action的逻辑拆到独立静态类里保持代码整洁,又不想每次加新Action都手动去维护那个List,还纠结接口不能静态的问题对吧?给你几个实用的解决思路,挑适合你的来:

方案1:反射+特性自动扫描(最省心的批量处理)

核心思路是用标记特性给每个静态Action类打标签,然后在程序启动时通过反射自动扫描所有带标签的类,把它们的执行方法收集到列表/字典里,完全不用手动维护。

步骤1:定义标记特性

[AttributeUsage(AttributeTargets.Class)]
public class StringActionHandlerAttribute : Attribute { }

步骤2:拆分每个Action到独立静态类

给每个类打上特性标记,把逻辑封装在Execute方法里:

[StringActionHandler]
public static class ReverseStringHandler
{
    public static void Execute(string input)
    {
        Console.WriteLine(new string(input.Reverse().ToArray()));
    }
}

[StringActionHandler]
public static class ConditionalCatHandler
{
    public static void Execute(string input)
    {
        if (input.Equals("Cat"))
        {
            // 用字典按类名调用比索引更安全(避免列表顺序变动出问题)
            ActionMap[nameof(ReverseStringHandler)](input);
        }
        else
        {
            ActionMap[nameof(DoublePrintHandler)](input);
        }
    }
}

[StringActionHandler]
public static class DoublePrintHandler
{
    public static void Execute(string input)
    {
        Console.WriteLine(input);
        Console.WriteLine(input);
    }
}

步骤3:启动时自动收集

在程序初始化的地方(比如静态构造函数、模块初始化器)用反射扫描并构建列表/字典:

public static List<Action<string>> ActionList { get; private set; }
public static Dictionary<string, Action<string>> ActionMap { get; private set; }

static ActionManager()
{
    // 扫描所有带标记的静态类
    var handlerTypes = AppDomain.CurrentDomain.GetAssemblies()
        .SelectMany(asm => asm.GetTypes())
        .Where(t => t.IsStatic && t.GetCustomAttribute<StringActionHandlerAttribute>() != null);

    ActionMap = new Dictionary<string, Action<string>>();
    foreach (var type in handlerTypes)
    {
        // 获取静态的Execute方法
        var executeMethod = type.GetMethod("Execute", new[] { typeof(string) });
        if (executeMethod != null)
        {
            var action = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), executeMethod);
            ActionMap.Add(type.Name, action);
        }
    }
    ActionList = ActionMap.Values.ToList();
}

优点:新增Action只需要加带特性的静态类,完全不用改列表;用字典按类名调用比索引更可靠,避免列表顺序变动导致的错误。
缺点:启动时会有一点点反射开销,但几百个类的规模完全可以忽略。

方案2:静态工厂+手动注册(更可控的轻量方案)

如果不想用反射,也可以用静态工厂类,让每个Action类自己注册到工厂里,兼顾可控性和模块化。

步骤1:实现静态工厂

public static class ActionFactory
{
    private static readonly List<Action<string>> _actions = new();
    private static readonly Dictionary<string, Action<string>> _actionMap = new();

    public static IReadOnlyList<Action<string>> Actions => _actions.AsReadOnly();
    public static IReadOnlyDictionary<string, Action<string>> ActionMap => _actionMap.AsReadOnly();

    public static void Register(string handlerName, Action<string> action)
    {
        if (!_actionMap.ContainsKey(handlerName))
        {
            _actionMap.Add(handlerName, action);
            _actions.Add(action);
        }
    }
}

步骤2:每个Action类自动注册

利用静态构造函数或者C# 9+的ModuleInitializer来完成注册:

public static class ReverseStringHandler
{
    // 静态构造函数:类第一次被访问时自动执行注册
    static ReverseStringHandler()
    {
        ActionFactory.Register(nameof(ReverseStringHandler), Execute);
    }

    public static void Execute(string input)
    {
        Console.WriteLine(new string(input.Reverse().ToArray()));
    }
}

// 用ModuleInitializer统一触发所有类的注册(可选,确保所有类都被加载)
[ModuleInitializer]
public static void InitializeAllHandlers()
{
    // 触发每个类的静态构造函数
    _ = typeof(ReverseStringHandler);
    _ = typeof(ConditionalCatHandler);
    // ... 其他Action类
}

优点:没有反射开销,完全可控;适合对启动性能有要求的场景。
缺点:新增Action时需要记得在ModuleInitializer里加一行触发代码,或者依赖类被访问时自动注册(如果有Action从未被调用,可能不会被注册)。

方案3:接口+单例实例(兼顾规范和静态行为)

虽然接口不能是静态的,但可以让静态类实现非静态接口,再通过单例实例来统一管理——既用接口约束所有Action的规范,又保持静态类的行为模式。

步骤1:定义约束接口

public interface IStringActionHandler
{
    void Execute(string input);
}

步骤2:静态类实现接口并提供单例

public static class ReverseStringHandler : IStringActionHandler
{
    // 公开单例实例供外部调用
    public static readonly IStringActionHandler Instance = new ReverseStringHandler();

    // 私有构造函数,禁止外部实例化
    private ReverseStringHandler() { }

    public void Execute(string input)
    {
        Console.WriteLine(new string(input.Reverse().ToArray()));
    }
}

步骤3:扫描收集实例

同样用反射扫描所有实现接口的静态类,提取单例实例:

static ActionManager()
{
    var handlerTypes = AppDomain.CurrentDomain.GetAssemblies()
        .SelectMany(asm => asm.GetTypes())
        .Where(t => t.IsStatic && typeof(IStringActionHandler).IsAssignableFrom(t));

    ActionList = new List<Action<string>>();
    foreach (var type in handlerTypes)
    {
        var instanceProp = type.GetProperty("Instance", typeof(IStringActionHandler));
        if (instanceProp != null)
        {
            var handler = (IStringActionHandler)instanceProp.GetValue(null);
            ActionList.Add(handler.Execute);
        }
    }
}

优点:有接口约束,确保所有Action都遵循统一的方法签名;代码规范度更高。
缺点:每个类需要写单例实例的代码,稍微有点冗余。


总结推荐

如果你的场景是几百个Action且需要频繁新增,方案1(反射+特性)是最省心的,完全解放手动维护列表的工作量;如果追求极致性能或者可控性,方案2更合适;如果需要强接口规范,方案3是不错的选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:40:12