Microsoft.Win32.SystemEvents 事件不适用于 WeakEventManager

Microsoft.Win32.SystemEvents events don't work with WeakEventManager

当我做的时候

WeakEventManager<SystemEvents, EventArgs>
    .AddHandler(null, nameof(SystemEvents.DisplaySettingsChanged), OnDisplaySettingsChanged);

我的 OnDisplaySettingsChanged 从来没有被调用过。但是,如果我改为通过 SystemEvents.DisplaySettingsChanged += OnDisplaySettingsChanged 使用普通事件订阅,一切正常。

怎么回事?

原来是WeakEventManager的错。当事件被触发时,它意味着 source 将是 null 对于静态事件源(代码摘自 reference source):

protected void DeliverEvent(object sender, EventArgs args)
{
    ListenerList list;
    object sourceKey = (sender != null) ? sender : StaticSource;
    ...

但是对于 SystemEventssender 永远不会是 null。相反,它传递 SystemEventsWeakEventManager 的私有实例,然后假设它是另一个它之前不知道的实例,并且不调用处理程序。

这是我想出的解决方法:

class EventProxy
{
    private readonly Action<EventHandler> _subscribe;
    private readonly Action<EventHandler> _unsubscribe;

    public EventProxy(Action<EventHandler> subscribe, Action<EventHandler> unsubscribe)
    {
        _subscribe = subscribe;
        _unsubscribe = unsubscribe;
    }

    private EventHandler _event;
    public event EventHandler Event
    {
        add
        {
            if (_event == null)
                _subscribe(OnEvent);
            _event += value;
        }
        remove
        {
            // ReSharper disable once DelegateSubtraction
            _event -= value;
            if (_event == null)
                _unsubscribe(OnEvent);
        }
    }

    private void OnEvent(object sender, EventArgs args)
    {
        _event?.Invoke(this, args);
    }
}

用法示例:

var proxy = new EventProxy(h => SystemEvents.DisplaySettingsChanged += h, h => SystemEvents.DisplaySettingsChanged -= h);
WeakEventManager<EventProxy, EventArgs>.AddHandler(proxy, nameof(EventProxy.Event), OnDisplaySettingsChanged);

一些解释:

  • SystemEvents 持有对 EventProxy 的强引用,后者持有对处理程序的弱引用(通过 WeakEventManager
  • WeakEventManager订阅了AddHandler里面的事件时,proxy订阅了原来的事件
  • EventProxy 充当静态事件和处理程序之间的代理,在原始事件触发时调用处理程序
  • 处理程序被收集后,WeakEventManager最终会运行清理,发现处理程序已死并取消订阅
  • 这将导致代理取消订阅原始事件,并最终被 GC 收集