Introduction

Let’s start the post by first stating the obvious:

ObservableCollections are awesome!

There, I said it.

Anyone that has done some WPF development will also admit that the use of ObservableCollection in conjunction with the MVVM pattern makes application development, and life, a lot easier.

However, like any control/data structure, you sometimes find it just doesn’t provide all the functionality that you need to perform certain tasks.

Now, if you are a regular user of ObservableCollection, or even a non-believer (at the moment), this post will give a brief overview of what the default ObservableCollection offers, and will highlight a couple of issues I have encountered over the years.

Rather than just pointing out the issues, we will also see how to “fix” them, and thereby improving the ObservableCollection for future use.

Ready? Let’s go.

Overview of the default ObservableCollection

So for the people that does not know what the ObservableCollection is, here is the MSDN definition:

Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.

I think the definition above is quite clear and concise as to what the ObservableCollection offers. The notifications it refer to work exceptionally well when it comes to MVVM and databinding.

These notifications are triggered via the CollectionChanged event, so whenever the items of the collection changes, properties that are bound to the ObservableCollection will be notified, and the UI/whatever can be updated with the new items. Powerful stuff.

It isn’t without a few missing features though. In the next section we will take a quick look at some of the features I think is missing from the default ObservableCollection.

Issues with ObservableCollection

Issue 1: Adding/Removing multiple items

Let us take a closer look at that definition again:

Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.

From the definition, is it safe to make the assumption that the change notifications will be triggered in the following pseudo calls:

  • list.Add(someItem)
  • list.Remove(someItem)
  • list.AddRange(someItemList)
  • list.ClearRange()

Got your answers ready? Here goes:

The answers:

  • 1+2: Yes
  • 3+4: No

You may ask why it will not trigger for the last 2 calls, and the answer is simple: ObservableCollection does not provide functionality to add/remove more than one list item at a time.

Your initial reaction might be something like, “who cares?” To show why this might be an issue, picture the following, not totally ridiculous scenario:

Your UI is databound to an ObservableCollection, and it is populated with a couple of items. Now we need to add quite a few, say 50, new items to the list. Easy right? We just use a foreach to traverse over the new items and add them one by one like so:

foreach(var item in itemList)
	list.Add(item)

Yeah, that will work, but it will trigger a change notification each time an item is added to the list. That means your databindings are updated 50 times. This can lead to some serious performance issues with you applications.

Sure, there might be ways around this limitation, like populating a seperate non-databound list, and then setting the ObservableCollection to that new list, but that will not work in all scenarios.

We will see how we can get past this limitation without too much trouble. Click here to go directly to the solution for this problem.

Issue 2: Only collection changes triggers notifications

The INotifyPropertyChanged interface is probably the most well known interface when it comes to WPF applications. It enables developers to properly leverage the databinding feature of WPF by automatically triggering change notifications when a property of an object changes.

Let’s take a look at a simple example:

public class Person : INotifyPropertyChanged
{
	private string _name;
	public string Name
	{
		get{ return _name; }
		set
		{
			if(_name==value)
				return;
			_name=value;
			NotifyPropertyChanged("Name");
		}
	}

	public event PropertyChangedEventHandler PropertyChanged;

 	private void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Assuming we have done the UI databinding correctly, whenever the Name property of the Person object changes, it will notify the UI to refresh itself to reflect the new value.

But if we have an ObservableCollection containing Person objects, will it update the UI if a Person object’s name changes? To put it bluntly: No.

That is because the ObservableCollection only triggers when the collection itself changes, and it does not monitor the objects it contains for changes.

To skip directly to the solution to this problem, click here.

If you are reading this, I take it you are a fan of my writing, and don’t want to miss anything by skipping ahead. :)

We will now address the issues pointed out, starting with the easiest one first.

Adding Collection Support

In this section we will provide the ObservableCollection the ability to add/remove multiple items to/from the collection, without raising notifications for each item.

The simplest way to achieve this is by extending the ObservableCollection.

We will create a new type of ObservableCollection, and simply call it RangeObservableCollection.

The logic of the new class is simple enough:

  • Add 2 new methods, namely AddRange and ClearRange.
  • Override the OnCollectionChanged event, to check if the notification is suppressed before raising the change notifications
  • In our new methods we then suppress notifications while we leverage the existing Add and ClearItems methods
  • Reset the suppression when we are done
  • Raise the notification ourselves after modifying the collection

When implemented the completed class will look as follow:

public class RangeObservableCollection<T> : ObservableCollection<T>
{
	private bool _suppressNotification = false;

	protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
	{
		if (!_suppressNotification)
			base.OnCollectionChanged(e);
	}

	public void AddRange(IEnumerable<T> list)
	{
		if (list == null)
			throw new ArgumentNullException("list");

		_suppressNotification = true;

		foreach (T item in list)
			Add(item);

		_suppressNotification = false;
		OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
	}

	public void ClearRange()
	{
		_suppressNotification = true;

		ClearItems();

		_suppressNotification = false;
		OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
	}
}

Something to note: When we trigger the change notifications after modifying the collection, we have to use the NotifyCollectionChangedAction.Reset value, to indicate that the entire list has changed. The default Add and Remove methods triggers NotifyCollectionChangedAction.Add and NotifyCollectionChangedAction.Remove actions respectively, for each item that is added/removed from the collection. We cannot use these actions because we are now only issueing one change notification, and thus cannot use the actions that are reserved for use on single items.

Not exactly rocket science so far, so let us go onto the next issue.

Adding Object Change Notification

Caveat: The solution we are going to implement will only work if the ObservableCollection contains items that implement the INotifyPropertyChanged interface.

Okay, don’t let the abovementioned warning scare you off. In almost all cases where we want the ObservableCollection to notify us of property changes in the items itself, those objects will most likely already be implementing the INotifyPropertyChanged interface.

Okay, let’s get to work.

Like the previous section, we will extend the ObservableCollection class by inheriting from it.

Let’s create a new class called ItemObservableCollection, and it will inherit from ObservableCollection, same as the RangeObservableCollection.

At the moment your new class should like something like this:

public class ItemObservableCollection<T> : ObservableCollection<T>
{
}

Since we will only cater for objects that implement the INotifyPropertyChanged interface, we need to limit the generic types that can be stored in the collection, by changing our new class definition as follow:

public class ItemObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
}

The logic for the new class will be:

  • Create new method that will raise change notification when a property on an item changes
  • Change the functionality of the CollectionChanged event, so that we can perform some custom logic
  • Unsubscribe all old items in the collection from property change notifications
  • Subscribe all new items to raise the new property change notifications

That’s it. The implemented class should look like this when done:

public class ItemObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
	public ItemObservableCollection() 
	{
		this.CollectionChanged += CollectionChanged_Handler;
	}

	void CollectionChanged_Handler(object sender, NotifyCollectionChangedEventArgs e)
	{
		//unsubscribe all old objects
		if (e.OldItems != null)
		{
			foreach (T x in e.OldItems)
				x.PropertyChanged -= ItemChanged;
		}

		//subscribe all new objects
		if (e.NewItems != null)
		{
			foreach (T x in e.NewItems)
				x.PropertyChanged += ItemChanged;
		}
	}

	private void ItemChanged(object sender, PropertyChangedEventArgs e)
	{
		OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
	}

}

There we have it. This new class will now raise a notification whenever one if it’s items has a property that changes.

Nice.

Extending ObservableCollection into awesomeness

Now you might be starting to ask yourself: where is this AwesomeObservableCollection that was alluded to? Hang on, we are almost there.

We have now seen how easy it is to work around some of the limitations of the default ObservableCollection by simply creating two new classes that provide some great additional functionality.

So what if we want to have an ObservableCollection that deals with both these issues? This is where the AwesomeObservableCollection comes in. It is simply a new class that will have the same functionality as the two previous classes in one place, with a few slight modifications.

When we create the new AwesomeObservableCollection class with the implementations exactly as we did it in the sections above, it should look something like this (seperated into regions for clarity):

public class AwesomeObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
	public AwesomeObservableCollection()
	{
		base.CollectionChanged += CollectionChanged_Handler;
	}

	#region RangeObservableCollection

	private bool _suppressNotification = false;

	protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
	{
		if (!_suppressNotification)
			base.OnCollectionChanged(e);
	}

	public void AddRange(IEnumerable<T> list)
	{
		if (list == null)
			throw new ArgumentNullException("list");

		_suppressNotification = true;

		foreach (T item in list)
		{
			Add(item);
		}
		_suppressNotification = false;

		OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
	}

	public void ClearRange()
	{
		_suppressNotification = true;

		ClearItems();

		_suppressNotification = false;
		OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
	}


	#endregion RangeObservableCollection

	#region ItemObservableCollection

	void CollectionChanged_Handler(object sender, NotifyCollectionChangedEventArgs e)
	{
		//unsubscribe all old objects
		if (e.OldItems != null)
		{
			foreach (T x in e.OldItems)
				x.PropertyChanged -= ItemChanged;
		}

		//subscribe all new objects
		if (e.NewItems != null)
		{
			foreach (T x in e.NewItems)
				x.PropertyChanged += ItemChanged;
		}
	}

	private void ItemChanged(object sender, PropertyChangedEventArgs e)
	{
		OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
	}

	#endregion ItemObservableCollection
}

However, we are not done yet. This is where things get a bit tricky. See the caveat when have to deal with when adding/removing ranges for a refresher of one more issue we have to deal with.

Since we are suppressing change notifications when adding/clearing ranges, the change notifications won’t trigger after each item has been added/removed from the collection with a NotifyCollectionChangedAction.Add or NotifyCollectionChangedAction.Remove action. It will only trigger the CollectionChanged handler once, with a NotifyCollectionChangedAction.Reset argument, as per our implementation. That means we won’t have any e.OldItems or e.NewItems in the CollectionChanged_Handler, so any items added/removed won’t be subscribing/unsubscribing to the property change notifications.

We will now need to add the code to handle these situations in our AddRange and ClearRange methods. Modify the methods to look like this:

public void AddRange(IEnumerable<T> list)
{
	if (list == null)
		throw new ArgumentNullException("list");

	_suppressNotification = true;

	foreach (T item in list)
	{
		Add(item);
		item.PropertyChanged += ItemChanged;
	}
	_suppressNotification = false;

	OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

public void ClearRange()
{
	_suppressNotification = true;

	foreach (T item in Items)
		item.PropertyChanged -= ItemChanged;

	ClearItems();

	_suppressNotification = false;
	OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

From the code you should be able to see that the only change we had to make was to simply subscribed and unsubscribed the items to the change notifications ourselves.

The final implementation of AwesomeObservableCollection is here for the sake of completeness, and because I like you guys.

public class AwesomeObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
	public AwesomeObservableCollection()
	{
		base.CollectionChanged += CollectionChanged_Handler;
	}

	#region RangeObservableCollection

	private bool _suppressNotification = false;

	protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
	{
		if (!_suppressNotification)
			base.OnCollectionChanged(e);
	}

	public void AddRange(IEnumerable<T> list)
	{
		if (list == null)
			throw new ArgumentNullException("list");

		_suppressNotification = true;

		foreach (T item in list)
		{
			Add(item);
			item.PropertyChanged += ItemChanged;
		}
		_suppressNotification = false;

		OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
	}

	public void ClearRange()
	{
		_suppressNotification = true;

		foreach (T item in Items)
			item.PropertyChanged -= ItemChanged;

		ClearItems();

		_suppressNotification = false;
		OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
	}


	#endregion RangeObservableCollection

	#region ItemObservableCollection

	void CollectionChanged_Handler(object sender, NotifyCollectionChangedEventArgs e)
	{
		//unsubscribe all old objects
		if (e.OldItems != null)
		{
			foreach (T x in e.OldItems)
				x.PropertyChanged -= ItemChanged;
		}

		//subscribe all new objects
		if (e.NewItems != null)
		{
			foreach (T x in e.NewItems)
				x.PropertyChanged += ItemChanged;
		}
	}

	private void ItemChanged(object sender, PropertyChangedEventArgs e)
	{
		OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
	}

	#endregion ItemObservableCollection
}

Conclusion

In this post we have gone through a couple of scenarios that the default ObservableCollection does not cope with very well, and looked at ways we can deal with it. In the process we have created 3 fully functional types of ObservableCollections that can be used depending on you specific scenarios.

For easy access to the final code implementations:

or you can download the source code from here:

Hope you enjoyed it, and learned something in the process.

Please feel free to comment with feedback/suggestions/compliments.

Thanks for reading.