首页 > 解决方案 > 失去耦合的通用事件处理程序实现,征求意见

问题描述

我使用中央事件处理程序实现了中介者模式,其他对象可以引发事件并从中接收事件。我的第一个实现使用了常规事件,并且会遇到垃圾收集问题,因为(相对)短寿命的对象会订阅来自寿命较长的对象的事件,所以我用 Wea​​kReference 重新设计了它。

我把一些东西放在一起,这似乎以一种使用弱引用的内存安全方式和一种灵活的方式使用泛型方法来解决问题,但是当垃圾收集运行时(用 GC.Collect() 测试)我意识到了一个问题,因为所有弱引用被杀死。

如果这个设计听起来不错,如果是这样,为什么指向活动方法的弱引用会被垃圾收集杀死?

我做了一个非常简单的自包含示例。

using System;
using System.Collections.Generic;

namespace Events
{
    class Program
    {
        static void Main(string[] args)
        {
            //int and bool are used for this example only to keep it very simple
            //In actual use it will be structs containing event data

            EventManager.RegisterEventHandler<int>(intHandler1);
            EventManager.RegisterEventHandler<bool>(boolHandler1);
            EventManager.RegisterEventHandler<int>(intHandler2);
            EventManager.RegisterEventHandler<bool>(boolHandler2);

            //generic type does not have to be explicit, since argument defines it
            //but I added for clarity here
            //raise a bool event and an int event, which each has two handlers at this point
            EventManager.RaiseEvent<bool>(true); 
            EventManager.RaiseEvent<int>(17);

            //remove one bool handler and then raise a bool event again
            //not only handled by the remaining handler
            EventManager.UnregisterEventHandler<bool>(boolHandler2);
            EventManager.RaiseEvent<bool>(false);

        }

        static void intHandler1(int i)
        {
            Console.WriteLine("IntHandler1 got " + i);
        }
        static void intHandler2(int i)
        {
            Console.WriteLine("IntHandler2 got " + i);
        }
        static void boolHandler1(bool b)
        {
            Console.WriteLine("BoolHandler1 got " + b);
        }
        static void boolHandler2(bool b)
        {
            Console.WriteLine("BoolHandler2 got " + b);
        }
    }


    public static class EventManager
    {
        //Given an instance of action<T>, create/find a list that can hold weak references
        //to that type of action, and add it to that list. Maintain a dictionary
        //referencing typeof(T) to the specific list holding that kind of action<T>
        public static void RegisterEventHandler<T>(Action<T> handler)
        {
            bool alreadyHasList = typeHandlerListDictionary.TryGetValue(typeof(T), out object handlerList);
            if (!alreadyHasList)
            {
                handlerList = new List<WeakReference<Action<T>>>();
                typeHandlerListDictionary.Add(typeof(T), handlerList);                
            }
            (handlerList as List<WeakReference<Action<T>>>).Add(new WeakReference<Action<T>>(handler));
        }

        //Given an instance of action<T>, find the list holding those actions as weak references 
        //and remove it from the list if found
        public static void UnregisterEventHandler<T>(Action<T> handler)
        {
            bool alreadyHasList = typeHandlerListDictionary.TryGetValue(typeof(T), out object handlerReferenceList);
            if (!alreadyHasList) throw new ArgumentException("The handler has not been registred");

            var list = handlerReferenceList as List<WeakReference<Action<T>>>;

            for (int i=0;i<list.Count;i++)
            {
                (list[i] as WeakReference<Action<T>>).TryGetTarget(out Action<T> thisHandler);
                if (thisHandler == handler)
                {
                    list.RemoveAt(i);
                    break;
                }
            }
        }

        //Given an argument of type T, find the list holding weak references to any number of action<T> which 
        //can then be called with the argument
        public static void RaiseEvent<T>(T arg)
        {
            bool hasHandlerListForThisEventType = typeHandlerListDictionary.TryGetValue(typeof(T), out object handlerReferenceList);
            if (!hasHandlerListForThisEventType) return;

            foreach (var handlerReference in handlerReferenceList as List<WeakReference<Action<T>>>)
            {
                if (handlerReference.TryGetTarget(out Action<T> handlerAction))
                {
                    handlerAction.Invoke(arg);
                }
            }
        }

        //dictionary referencing typeof(T) to lists holding weak references to instances of action<T>
        static Dictionary<Type, object> typeHandlerListDictionary = new Dictionary<Type, object>();
    }
}

标签: c#genericsevent-handling

解决方案


好吧,这很愚蠢。在输入问题,发现一些问题并解决这些问题后,我就有了答案。这可能会引起其他人的兴趣,所以我会回答并留下它。如果您觉得它不合适,请投票关闭并删除。

当我将一个动作传递给 EventManager 时,该动作可能指向对象中的一个方法,该方法仍然存在,但 WeakReference 不是指向具有处理程序方法的对象,而是指向动作,并且该动作本身具有没有任何东西指向它,除了 WeakReference,因此它可以被收集。

然后,解决方案可以是在处理程序对象中创建操作,并将其存储为成员,以便在处理程序对象存在时,根据需要引用它。

如果这对其他人来说似乎无关紧要,请投票关闭,这就是它的结束。


推荐阅读