简单的事件系统,无法从列表中删除侦听器

时间:2019-06-28 07:40:13

标签: c#

我通过按照教程制作了一个简单的事件系统,侦听器的注册和触发事件运行良好,但是我无法从中删除任何侦听器。

    delegate void EventListener(EventInfoBase eventInfo);
    Dictionary<System.Type, List<EventListener>> eventListeners;

    public void RegisterListener<T>(System.Action<T> listener) where T : EventInfoBase
    {
        System.Type eventType = typeof(T);

        if (eventListeners == null)
        {
            eventListeners = new Dictionary<System.Type, List<EventListener>>();
        }

        if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
        {
            eventListeners[eventType] = new List<EventListener>();
        }

        EventListener wrapper = (ei) => { listener((T)ei); };

        eventListeners[eventType].Add(wrapper);

    }

    public void UnregisterListener<T>(System.Action<T> listener) where T : EventInfoBase
    {
        System.Type eventType = typeof(T);

        if (eventListeners == null)
        {
            return;
        }

        if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
        {
            return;
        }

        EventListener wrapper = (ei) => { listener((T)ei); };

        EventListener toRemove = eventListeners[eventType].Find(x => x.Equals(wrapper));
        //EventListener toRemove = eventListeners[eventType].Find(x => x.Target == wrapper.Target && x.Method == wrapper.Method);

        if (toRemove != null)
        {
            eventListeners[eventType].Remove(toRemove); // Never gets called
        }

    }

这就是它的称呼(单身):

EventsSystem.Instance.RegisterListener<EventInfoWin>(OnWin);
EventsSystem.Instance.UnregisterListener<EventInfoWin>(OnWin);

因此,我希望将侦听器从适当的列表中删除,但它会保留在那里。 UnregisterListener方法不执行任何操作。有什么方法可以快速修复它而无需重写所有内容?

2 个答案:

答案 0 :(得分:0)

您不能像以前那样使用包装委托。原因是,这正在添加时创建另一个“对象”,以后要删除它时将无法识别。 正如Jon Skeet所写,您可以直接将动作保存为对象。我已经对其进行了测试,但没有找到一种方法将EventInfoBase的 Action 放入EventInfoWin的 行为中。

这就是它的样子:

编辑:我再次创建了一个包装器,但是将原始操作作为令牌来再次找到它。

    delegate void EventListener(EventInfoBase eventInfo);

    private class EventWrapper
    {
        public EventListener Action { get; set; }
        public object Token { get; set; }
    }

    Dictionary<System.Type, List<EventWrapper>> eventListeners = new Dictionary<System.Type, List<EventWrapper>>();


    public void RegisterListener<T>(System.Action<T> listener) where T : EventInfoBase
    {
        System.Type eventType = typeof(T);

        if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
        {
            eventListeners[eventType] = new List<EventWrapper>();
        }

        EventListener action = (ei) => { listener((T)ei); };
        var wrapper = new EventWrapper() { Action = action, Token = listener };

        eventListeners[eventType].Add(wrapper);

    }

    public void UnregisterListener<T>(System.Action<T> listener) where T : EventInfoBase
    {
        System.Type eventType = typeof(T);

        if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
        {
            return;
        }

        var toRemove = eventListeners[eventType].FirstOrDefault(x => x.Token.Equals(listener));

        if (toRemove != null)
        {
            eventListeners[eventType].Remove(toRemove);
        }
    }

答案 1 :(得分:0)

您的问题是,您在RegisterListener<T>中创建了一个EventListener类型的匿名包装方法,而在UnregisterListener<T>中创建了一个另一个包装方法。这些包装器永远不会匹配。

您可以做的是将原始侦听器与包装器一起存储。这将允许您与原始侦听器匹配,但是可以执行包装器(要执行原始侦听器,您需要进行反射-因此需要包装器)。如果您从字典切换到元组列表,则可以通过一种简单的方式做到这一点:

delegate void EventListener(object eventInfo);
List<(System.Type Type, Delegate Listener, EventListener Wrapper)> eventListeners;

public void RegisterListener<T>(System.Action<T> listener)
{
    System.Type eventType = typeof(T);

    if (eventListeners == null)
    {
        eventListeners = new List<(System.Type, Delegate, EventListener)>();
    }

    if (!eventListeners.Any(entry => entry.Type.Equals(eventType) &&
        entry.Listener.Equals(listener))) {

        eventListeners.Add((eventType, listener, ei => listener((T)ei)));
    }
}

public void UnregisterListener<T>(System.Action<T> listener)
{
    System.Type eventType = typeof(T);

    if (eventListeners == null)
    {
        return;
    }

    var toRemove = eventListeners.FirstOrDefault(entry => entry.Type.Equals(eventType) &&
        entry.Listener.Equals(listener));

    eventListeners.Remove(toRemove);
}

您可以尝试here