假设我有一个类,FruitManager,定期从一些数据源接收Fruit对象.我还有一些其他类,需要收到这些Fruit对象时收到通知.然而,每个类只对某些类型的水果感兴趣,每个水果对于如何处理都有不同的逻辑.例如,CitrusLogic类具有OnFruitReceived(Orange o)和OnFruitReceived(Lemon l)的方法,当收到相应的水果子类时,应该调用它,但不需要收到其他结果的通知.
有没有办法优雅处理这个在C#(大概与事件或代表)?显然,我可以添加通用的OnFruitReceived(Fruit f)事件处理程序,并使用if语句来过滤不需要的子类,但这似乎是不合适的.有没有人有更好的主意?谢谢!
编辑:我刚刚发现了generic delegates,他们似乎是一个很好的解决方案.这听起来像一个好的方向吗?
解决方法
移动到你的问题,C#中的一般事件模式是使用代理和事件关键字.由于您只需要处理程序调用,如果传入的水果与其方法定义兼容,则可以使用字典来完成查找.诀窍是什么类型存储代理.你可以使用一点魔法来使它工作,并存储所有东西
Dictionary<Type,Action<Fruit>> handlers = new Dictionary<Type,Action<Fruit>>();
这不是理想的,因为现在所有的处理程序似乎都接受了Fruit而不是更具体的类型.然而,这只是内部表示,公众人士仍然会通过这种方式添加特定的处理程序
public void RegisterHandler<T>(Action<T> handler) where T : Fruit
这样可以保持公共API的清洁和类型特定.在内部,代表需要从Action< T>动作< Fruit>.要做到这一点,创建一个新的委托,接收一个水果,并将其转换成一个T.
Action<Fruit> wrapper = fruit => handler(fruit as T);
这当然不是一个安全的演员.如果传递任何不是T(或从T继承)的任何东西,它将崩溃.这就是为什么它非常重要,它只存储在内部,而不是暴露在课外.将此功能存储在处理程序字典中的类型键类型(T)下.
在调用事件旁边需要一个自定义函数.此函数需要将所有的事件处理程序从继承链中的参数类型调用到最通用的Fruit处理程序.这允许在任何子类型参数上触发函数,而不仅仅是其特定类型.这对我来说似乎是直观的行为,但如果需要,可以省略.
最后,一个正常的事件可以被暴露,以允许所有的水果处理程序以通常的方式添加.
下面是完整的例子.请注意,该示例相当少,并排除了一些典型的安全检查,如空检查.如果没有从小到母的继承链,还有一个潜在的无限循环.一个实际的实现应该被视为合适.它也可以使用一些优化.特别是在高使用情况下缓存继承链可能很重要.
public class Fruit { } class FruitHandlers { private Dictionary<Type,Action<Fruit>>(); public event Action<Fruit> FruitAdded { add { handlers[typeof(Fruit)] += value; } remove { handlers[typeof(Fruit)] -= value; } } public FruitHandlers() { handlers = new Dictionary<Type,Action<Fruit>>(); handlers.Add(typeof(Fruit),null); } static IEnumerable<Type> GetInheritanceChain(Type child,Type parent) { for (Type type = child; type != parent; type = type.BaseType) { yield return type; } yield return parent; } public void RegisterHandler<T>(Action<T> handler) where T : Fruit { Type type = typeof(T); Action<Fruit> wrapper = fruit => handler(fruit as T); if (handlers.ContainsKey(type)) { handlers[type] += wrapper; } else { handlers.Add(type,wrapper); } } private void InvokeFruitAdded(Fruit fruit) { foreach (var type in GetInheritanceChain(fruit.GetType(),typeof(Fruit))) { if (handlers.ContainsKey(type) && handlers[type] != null) { handlers[type].Invoke(fruit); } } } }