如何编写可维护的编译时List<Action<T>>,支持元素间互相调用
嘿,这个需求太常见了——既要把每个Action的逻辑拆到独立静态类里保持代码整洁,又不想每次加新Action都手动去维护那个List,还纠结接口不能静态的问题对吧?给你几个实用的解决思路,挑适合你的来:
核心思路是用标记特性给每个静态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只需要加带特性的静态类,完全不用改列表;用字典按类名调用比索引更可靠,避免列表顺序变动导致的错误。
缺点:启动时会有一点点反射开销,但几百个类的规模完全可以忽略。
如果不想用反射,也可以用静态工厂类,让每个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从未被调用,可能不会被注册)。
虽然接口不能是静态的,但可以让静态类实现非静态接口,再通过单例实例来统一管理——既用接口约束所有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

