创建一个特殊字典<t,eventdelegate <t =“”>&gt;与通用委托EventDelegate <t>(T e)其中T:GameEventBase </t> </t,>

时间:2014-04-17 02:16:47

标签: c# events generics dictionary

我有一个游戏,有很多需要听事件的课程。但在某些情况下,这些类被销毁或被禁用。当发生这种情况时,我需要从事件管理器委托表中删除他们的监听方法。

我不想修改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都来自GameEventBaseOnClickHandler是声明为

的方法
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) {}

这个要求转化为我想要特殊字典中的类型安全。

我的一次尝试不是字典,而是在AddListenerRemoveListener之间决定调用哪种方法的方法 我不喜欢它,因为它为方法引入了一个参数,代码读取真的很奇怪。它确实有效,并确实减少了重复,但太难看了。

我创建了一个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);
        }
    }
}    

2 个答案:

答案 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#中改写了重写的选项。