Table of Contents
Weak Events
Implemented with strong event handlers, NotifyParentObservableCollection<T>
did what it was supposed to do. However, my first article received a lot of negative feedback.
My collection was supposed to replace functionality offered by BindingList<T>
(forwarding of INotifyPropertyChanged
events of its children).
I was told that BindingList<T>
attaches to PropertyChanged
events through weak references (which is apparently not the case, but anyway), while my collection uses strong references. This is a potential memory leak, and the programmer using NotifyParentObservableCollection<T>
would have to call Clear()
to release those references.
OK, time to stop being lazy and implement the WeakEvents pattern.
I knew that MSFT has WeakEventManager
, but just in case, I decided to surf the Web for the best WeakEvents implementation. To quote Nathan Nesbit[^]:
"There are many examples on the web for implementing some kind of weak listener - but interestingly, almost all of them have some flaw. In the end, we settled on using WeakEventManager
and its associated IWeakEventListener
interface."
All other solutions create an instance of the wrapper class attached to the source with a strong reference, and to the target with a weak reference. Every time the source event fires, the wrapper checks if the target is still alive. If the target is dead, the wrapper detaches from the source and can be garbage collected.
Unfortunately, this is a potential memory leak. If the source events never fire, thousands of wrappers can be leaked: Solving the Problem with Events: Weak Event Handlers[^], Weak Events in C#[^].
Looks like that the MSFT way makes most sense: create a single static event wrapper per event type, and clean up unused connections on the background thread.
I renamed my collection StrongNotifyParentObservableCollection<T>
and re-implemented NotifyParentObservableCollection<T>
(as well as the demo application) using WeakEventManager
and IWeakEventListener
. I wrote couple classes implementing WeakEventManager
(ChildPropertyChangedEventManager
and AnyPropertyChangedEventManager
), and implemented IWeakEventListener
in NotifyParentObservableCollection<T>
, as well as in the ViewModel.
Voila, there is no strong references left anymore, everything is weak now! My NotifyParentObservableCollection<T>
forwards children INPC events to interested listeners using WeakEventManager
, just like BindingList<T>
supposedly did.
Goal is achieved. Weak is strong. Chinese philosophy is right once again.
NotifyParentObservableCollection<T>
private void AttachHandlers(IEnumerable items)
{
if (items != null)
{
foreach (var item in items.OfType<inotifypropertychanged>())
{
AnyPropertyChangedEventManager.AddListener(item, this);
}
}
}
private void DetachHandlers(IEnumerable items)
{
if (items != null)
{
foreach (var item in items.OfType<inotifypropertychanged>())
{
AnyPropertyChangedEventManager.RemoveListener(item, this);
}
}
}
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(AnyPropertyChangedEventManager))
{
if (SupportsChangeNotifications)
OnChildPropertyChanged(this,
sender,
((PropertyChangedEventArgs)e).PropertyName);
}
return true;
}
void OnChildPropertyChanged(object sender, object source, string property)
{
var childPropertyChanged = ChildPropertyChanged;
if (childPropertyChanged != null)
{
childPropertyChanged(sender,
new ChildPropertyChangedEventArgs(source, property));
}
}
AnyPropertyChangedEventManager
public class AnyPropertyChangedEventManager : WeakEventManager
{
private AnyPropertyChangedEventManager() { }
public static void AddListener(INotifyPropertyChanged source,
IWeakEventListener listener)
{
CurrentManager.ProtectedAddListener(source, listener);
}
public static void RemoveListener(INotifyPropertyChanged source,
IWeakEventListener listener)
{
CurrentManager.ProtectedRemoveListener(source, listener);
}
protected override void StartListening(object source)
{
((INotifyPropertyChanged)source).PropertyChanged += DeliverEvent;
}
protected override void StopListening(object source)
{
((INotifyPropertyChanged)source).PropertyChanged -= DeliverEvent;
}
private static AnyPropertyChangedEventManager CurrentManager
{
get
{
var managerType = typeof(AnyPropertyChangedEventManager);
var manager = (AnyPropertyChangedEventManager)
GetCurrentManager(managerType);
if (manager == null)
{
manager = new AnyPropertyChangedEventManager();
SetCurrentManager(managerType, manager);
}
return manager;
}
}
}
ViewModel
private NotifyParentObservableCollection<LookupItem> _selectors;
public NotifyParentObservableCollection<LookupItem> Selectors
{
get { return _selectors; }
private set
{
if (_selectors != null)
{
ChildPropertyChangedEventManager.RemoveListener(_selectors, this);
CollectionChangedEventManager.RemoveListener(_selectors, this);
}
_selectors = value;
if (_selectors != null)
{
ChildPropertyChangedEventManager.AddListener(_selectors, this);
CollectionChangedEventManager.AddListener(_selectors, this);
}
OnPropertyChanged("Selectors");
}
}
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(CollectionChangedEventManager))
{
if (sender == Selectors)
OnSelectorsCollectionChanged(sender, (NotifyCollectionChangedEventArgs)e);
}
else if (managerType == typeof(ChildPropertyChangedEventManager))
{
if (sender == Selectors)
OnSelectorsChildPropertyChanged(sender, (ChildPropertyChangedEventArgs)e);
else if (sender == Words)
OnWordsChildPropertyChanged(sender, (ChildPropertyChangedEventArgs)e);
}
else if (managerType == typeof(AnyPropertyChangedEventManager))
{
}
return true;
}
ChildPropertyChangedEventManager
public class ChildPropertyChangedEventManager : WeakEventManager
{
private ChildPropertyChangedEventManager() { }
public static void AddListener(IChildPropertyChanged source,
IWeakEventListener listener)
{
CurrentManager.ProtectedAddListener(source, listener);
}
public static void RemoveListener(IChildPropertyChanged source,
IWeakEventListener listener)
{
CurrentManager.ProtectedRemoveListener(source, listener);
}
protected override void StartListening(object source)
{
((IChildPropertyChanged)source).ChildPropertyChanged += DeliverEvent;
}
protected override void StopListening(object source)
{
((IChildPropertyChanged)source).ChildPropertyChanged -= DeliverEvent;
}
private static ChildPropertyChangedEventManager CurrentManager
{
get
{
var managerType = typeof(ChildPropertyChangedEventManager);
var manager = (ChildPropertyChangedEventManager)
GetCurrentManager(managerType);
if (manager == null)
{
manager = new ChildPropertyChangedEventManager();
SetCurrentManager(managerType, manager);
}
return manager;
}
}
}
Revision History
- January 23, 2011 - Created the article.