The PropertyChanged event notifies us when a property of an object changes, which covers most of our GUI notification needs. However, there are some concepts that aren’t covered by it - specifically, when an item is added or removed from a collection. We use a different event, NotifyCollectionChanged to convey when this occurs.

The INotifyCollectionChanged Interface

The INotifyCollectionChanged interface defined in the System.Collections.Specialized namespace indicates the collection implements the NotifyCollectionChangedEventHandler, i.e.:

public event NotifyCollectionChangedEventHandler NotifyCollectionChanged;

And, as you would expect, this event is triggered any time the collection’s contents change, much like the PropertyChanged event we discussed earlier was triggered when a property changed. However, the NotifyCollectionChangedEventArgs provides a lot more information than we saw with the PropertyChangedEventArgs,as you can see in the UML diagram below:

UML or NotifyCollectionChangedEventArgs and PropertyChangedEventArgs

With PropertyChangedEventArgs we simply provide the name of the property that is changing. But with NotifyCollectionChangedEventArgs, we are describing both what the change is (i.e. an Add, Remove, Replace, Move, or Reset), and what item(s) we affected. So if the action was adding an item, the NotifyCollectionChangedEventArgs will let us know what item was added to the collection, and possibly at what position it was added at.

When implementing the INotifyCollectionChanged interface, you must supply a NotifyCollectionChangedEventArgs object that describes the change to the collection. This class has multiple constructors, and you must select the correct one, or your code will cause a runtime error when the event is invoked.

Info

You might be wondering why PropertyChangedEventArgs contains so little information compared to NotifyCollectionChangedEventArgs. Most notably, the old and new values of the property could have been included. I suspect the reason they were not is that there are many times where you don’t actually need to know what the property value is - just that it changed, and you can always retrieve that value once you know you need to.

In contrast, there are situations where a GUI displaying a collection may have hundreds of entries. Identifying exactly which ones have changed means that only those entries need to be modified in the GUI. If we didn’t have that information, we’d have to retrieve the entire collection and re-render it, which can be a very computationally expensive process.

But ultimately, the two interfaces were developed by different teams at different times, which probably accounts for most of the differences.

NotifyCollectionChangedAction

The only property of the NotifyCollectionChangedArgs that will always be populated is the Action property. The type of htis property is the NotifyCollectionChangedAction enumeration, and its values (and what they represent) are:

  • NotifyCollectionChangedAction.Add - one or more items were added to the collection
  • NotifyCollectionChangedAction.Move - an item was moved in the collection
  • NotifyCollectionChangedAction.Remove - one or more items were removed from the collection
  • NotifyCollectionChangedAction.Replace - an item was replaced in the collection
  • NotifyCollectionChangedAction.Reset - drastic changes were made to the collection

NotifyCollectionChangedEventArgs Constructors

A second feature you probably noticed from the UML is that there are a lot of constructors for the NotifyCollectionChangedEventArgs. Each represents a different situation, and you must pick the appropriate one.

For example, the NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction) constructor represents a NotifyCollectionChangedAction.Reset change. This indicates the collection’s content changed dramatically, and the best recourse for a GUI is to ask for the full collection again and rebuild the display. You should only use this one-argument constructor for a Reset action.

Warning

In C#, there is no mechanism for limiting a constructor to specific argument values. So you actually can call the above constructor for a different kind of event, i.e.:

new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add);

However, doing so will throw a InvalidArgumentException when the code is actually executed.

In general, if you are adding or removing an object, you need to provide the object to the constructor. If you are adding or removing multiple objects, you will need to provide an IList of the affected objects. And you may also need to provide the object’s index in the collection. You can read more about the available constructors and their uses in the Microsoft Documentation.