使用C#反射构建同基类派生Controller泛型列表问题
问题说明
需求是自动扫描MenuSystem类中所有菜单控制器字段,统一执行初始化、销毁逻辑,后续新增菜单时只需要加字段声明,不需要重复编写调用代码。
当前反射获取字段后,通过OfType<MenuController<BaseMenu>>筛选无法得到任何结果,没法统一填充列表遍历调用。
问题根因
C#泛型类默认不支持协变:即使MainMenu、PlayerMenu都继承自BaseMenu,MenuController<MainMenu>和MenuController<BaseMenu>也不存在继承/转换关系,直接做类型筛选会匹配失败,所以拿不到结果。
解决方案
推荐方案:新增非泛型基类(类型安全、无额外反射开销)
这是这类泛型场景下的标准实现方式,把不需要依赖泛型参数的公共方法抽到非泛型基类中,从根源解决类型无法统一的问题。
- 修改原有基类结构:
// 非泛型基类,定义所有菜单控制器的公共行为 public abstract class MenuController { public abstract void RetrieveMenuInfo(); public abstract void Dispose(); } // 原有泛型基类继承非泛型基类 public abstract class MenuController<T> : MenuController where T : BaseMenu { public override void RetrieveMenuInfo() { /* 保留原有实现逻辑 */ } public override void Dispose() { /* 保留原有实现逻辑 */ } }
- 实现自动扫描填充逻辑:
private IList<MenuController> _menuList; private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public; private void FillMenuList() { _menuList = GetType().GetFields(Flags) .Where(field => field.FieldType.IsSubclassOf(typeof(MenuController))) .Select(field => (MenuController)field.GetValue(this)) .ToList(); }
后续初始化、销毁逻辑直接遍历_menuList调用对应方法即可,完全不需要额外反射,新增菜单只要在类中声明对应字段,就能被自动扫描到,不需要修改其他调用逻辑。
兼容方案:不修改基类,通过反射判断泛型类型
如果不想改动原有基类的继承结构,可以直接在反射时判断字段类型是否为MenuController<>的派生类,通过反射缓存方法信息统一调用。
private IList<object> _menuList; private MethodInfo _retrieveMethod; private MethodInfo _disposeMethod; private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public; private void FillMenuList() { Type openMenuType = typeof(MenuController<>); _menuList = new List<object>(); // 提前缓存公共方法,避免每次调用重复反射查找 _retrieveMethod = openMenuType.GetMethod("RetrieveMenuInfo"); _disposeMethod = openMenuType.GetMethod("Dispose"); foreach (var field in GetType().GetFields(Flags)) { Type fieldBaseType = field.FieldType.BaseType; // 判断字段是否为MenuController<>的闭合泛型派生类 if (fieldBaseType != null && fieldBaseType.IsGenericType && fieldBaseType.GetGenericTypeDefinition() == openMenuType) { _menuList.Add(field.GetValue(this)); } } } // 调用初始化 private void InitializeAllMenus() { foreach (var menu in _menuList) { _retrieveMethod.Invoke(menu, null); } } // 调用销毁 private void DisposeAllMenus() { foreach (var menu in _menuList) { _disposeMethod.Invoke(menu, null); } }
注意:该方案有少量反射开销,务必提前缓存
MethodInfo,不要在每次调用方法时重新反射查找。
内容的提问来源于stack exchange,提问作者Sred
相关产品推荐
相关产品推荐

