Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

NotifyParentObservableCollection (Part 2) monitors changes of its children properties

0.00/5 (No votes)
4 Feb 2011 2  
ObservableCollection attaches to the PropertyChanged events of its children using the WeakEvents pattern, and raises the ChildPropertyChanged event accordingly. Replicates functionality of BindingList but without the overhead. Very useful when you need to update / select / arrange / move items.

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.

WeakEventWithWrapper.png

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))
    {
        // Put all your CollectionChanged event handlers here
        if (sender == Selectors)
            OnSelectorsCollectionChanged(sender, (NotifyCollectionChangedEventArgs)e);
    }
    else if (managerType == typeof(ChildPropertyChangedEventManager))
    {
        // Put all your ChildPropertyChanged event handlers here
        if (sender == Selectors)
            OnSelectorsChildPropertyChanged(sender, (ChildPropertyChangedEventArgs)e);
        else if (sender == Words)
            OnWordsChildPropertyChanged(sender, (ChildPropertyChangedEventArgs)e);
    }
    else if (managerType == typeof(AnyPropertyChangedEventManager))
    {
        // Put all your PropertyChanged event handlers here
    }
    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here