我有一个游戏,有很多需要听事件的课程。但在某些情况下,这些类被销毁或被禁用。当发生这种情况时,我需要从事件管理器委托表中删除他们的监听方法。
我不想修改EventsManager
,我希望每个类都添加任何事件来了解它添加的事件。
我目前正在使用类似的内容添加和删除每个类中的事件:
void AddEventsListeners() {
EventsManager.AddListener<OnClickDown>(OnClickDownHandler);
EventsManager.AddListener<OnClickUp>(OnClickUpHandler);
EventsManager.AddListener<OnClick>(OnClickHandler);
}
void RemoveEventsListeners() {
EventsManager.RemoveListener<OnClickDown>(OnClickDownHandler);
EventsManager.RemoveListener<OnClickUp>(OnClickUpHandler);
EventsManager.RemoveListener<OnClick>(OnClickHandler);
}
这些OnClick
都来自GameEventBase
,OnClickHandler
是声明为
void OnClickDown(OnClickHandler e) {}
匹配EventsManager
中使用的委托,该委托被声明为
delegate void EventDelegate<T>(T e) where T : GameEventBase;
我希望能够填充一个名为events
的特殊哈希表,它具有键值对,如
<T, EventDelegate<T>> where T: GameEventBase
也就是说,我希望能够events.add(OnClick, OnClickHandler)
,其中OnClickHandler
被声明为
OnClickHandler(OnClick e) {}
如果定义了OnClickHandler
,我希望添加失败,例如,
OnClickHandler(OtherGameEventBaseDerivedEvent e) {}
这个要求转化为我想要特殊字典中的类型安全。
我的一次尝试不是字典,而是在AddListener
和RemoveListener
之间决定调用哪种方法的方法
我不喜欢它,因为它为方法引入了一个参数,代码读取真的很奇怪。它确实有效,并确实减少了重复,但太难看了。
我创建了一个AddOrRemoveAllListeners(AddOrRemove addOrRemove)
,我为每个事件调用了AddOrRemoveListener
。
现在我所要做的只是AddOrRemoveAllListeners(AddOrRemove.Remove)
或AddOrRemoveAllListeners(AddOrRemove.Add)
,以添加或删除我的活动。
enum AddOrRemove {
Remove,
Add
}
void AddOrRemoveListener<T>(EventsManager.EventDelegate<T> del, AddOrRemove addOrRemove)
where T : GameEventBase {
switch (addOrRemove) {
case AddOrRemove.Remove:
EvMan.RemoveListener<T>(del);
break;
case AddOrRemove.Add:
EvMan.AddListener<T>(del);
break;
}
}
另一种尝试涉及创建类型
class EventsDictionary<T> : Dictionary<T, EventsManager.EventDelegate<T>> where T : GameEventBase { }
并像这样使用它:
EventsDictionary<GameEventBase> events = new MyCustomDictionary<GameEventBase>();
void AddEventHandlerPairToEventsDictionary<T>(T e, EventsManager.EventDelegate<T> handler) where T : GameEventBase {
if (!events.ContainsKey(e)) {
events.Add(e, handler);
}
}
但events.Add(e, handler)
失败并迫使我将handler
声明为
EventsManager.EventDelegate<GameEventBase>
而不是
EventsManager.EventDelegate<T>
如果我这样做,我可以添加在events
类型中没有意义的键值对,即我丢失了事件处理类型的安全性。
我想拥有这样的结构,因为我不喜欢所有这些重复。如果有人忘记删除RemoveEventsListeners()
中的活动,那将非常糟糕。
有了这样的字典,我可以使用foreach循环向EventsManager
添加/删除处理程序,这非常好。
至于性能,这是一个游戏,它需要有良好的性能。那些添加/删除事件可能会发生很多事情(有时每帧数百次),因为很多对象都被销毁(不能在事件管理器中留下空处理程序)或被禁用(需要停止监听所有事件,直到再次启用) 每时每刻。这意味着反射和许多铸造/装箱或任何产生大量垃圾收集对象的东西都出来了。
我当然可以就其他方法提出建议。
感谢您的协助!
我附加了正在使用的EventsManager的相关部分(RemoveListener()类似于AddListener)。 GameEventBase只是一个空shell。它不是.NET事件,也不使用EventArgs。
public class EventsManager : ManagedBase {
public delegate void EventDelegate<T>(T e) where T : GameEventBase;
private delegate void EventDelegate(GameEventBase e);
private readonly Dictionary<Type, EventDelegate> delegates = new Dictionary<Type, EventDelegate>();
private readonly Dictionary<Delegate, EventDelegate> delegateLookup = new Dictionary<Delegate, EventDelegate>();
public void AddListener<T>(EventDelegate<T> del) where T : GameEventBase {
// Early-out if we've already registered this delegate
if (delegateLookup.ContainsKey(del)) {
return;
}
// Create a new non-generic delegate which calls our generic one.
// This is the delegate we actually invoke.
EventDelegate internalDelegate = (e) => del((T) e);
delegateLookup[del] = internalDelegate;
EventDelegate tempDel;
if (delegates.TryGetValue(typeof (T), out tempDel)) {
delegates[typeof (T)] = tempDel + internalDelegate;
}
else {
delegates[typeof (T)] = internalDelegate;
}
}
public void Raise(GameEventBase e) {
EventDelegate del;
if (delegates.TryGetValue(e.GetType(), out del)) {
del.Invoke(e);
}
}
}
答案 0 :(得分:2)
如果使用EventAggregator模式,似乎可以解决您的问题。
有一个简短的描述by Martin Fowler
已经存在一些非常好的实现,例如caliburn micro和。{ Microsoft Prism
一般的想法是,您可以简化事件注册和注销,并为许多对象提供单一事件源。
我从来没有遇到过性能问题。如果要开始侦听对象的事件,则只需输入_eventAggregator.Subscribe(this),如果要停止,则取消订阅。无论你想发布一个事件,只需发布它,EventAggregator就可以进行路由。
答案 1 :(得分:1)
这再次看起来像一个XY问题。 OP似乎希望拥有处理事件处理程序,注册和处理的中心位置。 OP已经走上了尝试创建以通用方式处理这种模式的模式的路线,但没有研究关于如何通常解决这个问题的现有技术。他现在遇到了他设计中遇到的问题,现在要求解决这个问题,而不是事件处理程序的原始问题。
我知道在.net中有两个很好的事件处理程序注册生命周期管理解决方案。
弱事件处理程序
您声明“如果有人忘记删除RemoveEventsListeners()
中的某个活动,那将会非常糟糕。”然而,实际上并没有提到为什么它是坏的。通常,这种情况的唯一原因是事件处理程序现在将对象保留在引用中,应该收集该对象。使用弱引用事件处理程序,GC仍然可以收集您的对象,即使它订阅了仍然存在的对象。
<强> Rx.Net 强>
Rx.Net将事件注册抽象为IDisposables,您可以将其绑定到对象的生命周期,假设您当然希望控制注册的生命周期。
但实际上我发现IObservable
模式比事件处理程序模式更好用,主要是因为C#缺少对事件处理程序的一流支持(F#不是这种情况)。
<强> F#强>
您的大多数问题都源于C#中事件关键字处理的短视设计(特别是不使事件成为第一类构造)。然而,F#支持一等事件,因此应该能够支持您尝试构建的模式。
因此,使用此选项,您应该废弃代码库并在F#中重写它。
* EDIT在F#中改写了重写的选项。