Turns out it's WeakEventManager
's fault. When the event is fired, it implies that source
will be null
for static event sources (code excerpt from the reference source):
protected void DeliverEvent(object sender, EventArgs args)
{
ListenerList list;
object sourceKey = (sender != null) ? sender : StaticSource;
...
But sender
is never null
for SystemEvents
. Instead it passes a private instance of SystemEvents
, WeakEventManager
then assumes it's another instance it didn't previously know about and doesn't call the handler.
Here's the workaround I came up with:
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);
}
}
Usage example:
var proxy = new EventProxy(h => SystemEvents.DisplaySettingsChanged += h, h => SystemEvents.DisplaySettingsChanged -= h);
WeakEventManager<EventProxy, EventArgs>.AddHandler(proxy, nameof(EventProxy.Event), OnDisplaySettingsChanged);
Some explanation:
SystemEvents
holds a strong reference to EventProxy
, which holds a weak reference to the handler (via WeakEventManager
)
- When
WeakEventManager
subscribes to the event inside AddHandler
, the proxy subscribes to the original event
EventProxy
acts as a proxy between the static event and the handler, invoking the handler whenever the original event fires
- After the handler gets collected,
WeakEventManager
will eventually run a cleanup, discover that the handler is dead and unsubscribe
- This will cause the proxy to unsubscribe from the original event, and, eventually, get collected by GC