C#泛型接口调用优化:无需显式指定双泛型参数
解决泛型处理器的自动类型推导与游戏事件命令模式实现
问题背景
需要存储一批返回类型相同、参数类型不同的处理器对象,调用其方法时避免冗余代码。现有代码结构如下,存在多个IGenericBase<T>的实现(每个实现对应固定的参数类型T):
public interface IBase { } public interface IGenericBase<in T> : IBase { int GetNumber(T args); } public interface IConcrete : IGenericBase<decimal> { } public class Concrete : IConcrete { public int GetNumber(decimal args) { return (int) args; } }
核心诉求:
- 调用时无需在
GetNumbers<T1, T2>(T2 args)中显式指定两个泛型参数,希望通过处理器类型T1自动推导其对应的参数类型T2(T2在T1的实现中已固定)。 - 最终基于此实现游戏事件命令模式:当特定游戏事件(如
OnTurn、OnCardPlayed)触发时,传入对应事件数据,自动匹配并执行对应的处理器生成命令。
解决方案
1. 通用调用工具类(实现自动类型推导)
通过反射提取处理器实现的泛型接口参数类型,封装通用调用方法,避免手动指定泛型参数:
public static class ProcessorInvoker { // 弱类型调用:适用于通过容器匹配处理器的场景 public static int InvokeGetNumber<TProcessor>(TProcessor processor, object args) where TProcessor : IBase { var genericBaseType = typeof(TProcessor).GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IGenericBase<>)); if (genericBaseType == null) throw new InvalidOperationException($"{typeof(TProcessor).Name}未实现IGenericBase<T>接口"); var argType = genericBaseType.GetGenericArguments()[0]; var typedArgs = Convert.ChangeType(args, argType); var method = genericBaseType.GetMethod(nameof(IGenericBase<object>.GetNumber)); return (int)method.Invoke(processor, new[] { typedArgs }); } // 强类型调用:编译器自动推导泛型参数,无需手动指定 public static int InvokeGetNumber<TProcessor, TArgs>(TProcessor processor, TArgs args) where TProcessor : IGenericBase<TArgs> { return processor.GetNumber(args); } }
2. 处理器容器(适配事件自动匹配)
创建容器按事件数据类型注册处理器,事件触发时直接根据数据类型找到对应处理器执行:
public class ProcessorContainer { private readonly Dictionary<Type, IBase> _processors = new Dictionary<Type, IBase>(); // 自动识别处理器对应的事件数据类型并注册 public void Register<TProcessor>(TProcessor processor) where TProcessor : IBase { var genericBaseType = typeof(TProcessor).GetInterfaces() .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IGenericBase<>)); var eventDataType = genericBaseType.GetGenericArguments()[0]; _processors[eventDataType] = processor; } // 触发事件:传入事件数据自动匹配处理器 public int HandleEvent<TEventData>(TEventData eventData) { if (_processors.TryGetValue(typeof(TEventData), out var processor)) { return ProcessorInvoker.InvokeGetNumber(processor, eventData); } throw new KeyNotFoundException($"未找到处理{typeof(TEventData).Name}类型事件的处理器"); } }
3. 游戏事件适配示例
定义游戏事件数据类与对应处理器:
// 回合事件数据 public class TurnEventData { public int TurnNumber { get; set; } } // 卡牌打出事件数据 public class CardPlayedEventData { public string CardId { get; set; } public string PlayerId { get; set; } } // 回合事件处理器 public class TurnProcessor : IGenericBase<TurnEventData> { public int GetNumber(TurnEventData args) { // 根据回合数生成命令标识 return args.TurnNumber * 100; } } // 卡牌事件处理器 public class CardPlayedProcessor : IGenericBase<CardPlayedEventData> { public int GetNumber(CardPlayedEventData args) { // 根据卡牌信息生成命令标识 return args.CardId.GetHashCode(); } }
使用方式:
// 注册所有处理器 var container = new ProcessorContainer(); container.Register(new TurnProcessor()); container.Register(new CardPlayedProcessor()); // 触发回合事件 var turnEvent = new TurnEventData { TurnNumber = 5 }; int turnCommand = container.HandleEvent(turnEvent); // 触发卡牌打出事件 var cardEvent = new CardPlayedEventData { CardId = "Fireball", PlayerId = "Player1" }; int cardCommand = container.HandleEvent(cardEvent);
内容的提问来源于stack exchange,提问作者nuclear sweet
相关产品推荐
相关产品推荐

