失去耦合的通用事件处理程序实现,征求意见

Lose coupled generic event handler implementation, request for comments

我使用中央事件处理程序实现了调解器模式,其他对象可以在该事件处理程序上引发事件并从中接收事件。我的第一个实现使用常规事件,并且会出现垃圾收集问题,因为(相对)短寿命对象会订阅来自长寿命对象的事件,所以我用 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>();
    }
}

嗯,这很愚蠢。在输入问题、发现问题并解决这些问题后,我得到了答案。这可能会引起其他人的兴趣,所以我会回答并留下它。如果您觉得不妥,请投票关闭并移除。

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

然后,解决方案可以是在处理程序对象中创建操作,并将其存储为成员,以便在处理程序对象存在时引用它,正如所希望的那样。

如果这对其他人来说似乎无关紧要,请投票关闭,到此结束。