Click here to Skip to main content
11,639,554 members (61,750 online)
Click here to Skip to main content

Enhanced ObservableCollection with ability to delay or disable notifications

, 3 Apr 2012 CPOL 83K 3.1K 120
Rate this:
Please Sign up or sign in to vote.
Implements delayed or disabled INotifyCollectionChanged.
This is an old version of the currently published article.

Introduction

MSDN describes ObservableCollection as a dynamic data collection which provides notifications when items get added, removed, or when the whole list is refreshed.

ObservableCollection is fully bindable. It implements both INotifyPropertyChanged and INotifyCollectionChanged, so whenever the collection is changed, appropriate notification events are fired off immediately and bound objects are notified and updated.

This scenario works in most cases but sometimes it would be beneficial to postpone notifications until later or temporarily disable them all together. For example, until a batch update is finished. This notification delay could increase performance as well as eliminate screen flicker of updated visuals. Unfortunately, the default implementation of ObservableCollection does not provide this functionality.

ObservableCollectionEx is designed to provide this missing functionality. ObservableCollectionEx is designed as a direct replacement for ObservableCollection, is completely code compatible with it, and also provides a way to delay or disable notifications.

Background

In order to postpone notifications, we have to temporarily reroute them to a holding place and fire them all once delay is no longer required. At the same time, we need to continue to provide normal behavior and notifications for other consumers of the collection which do not require delay.

This could be achieved if we have multiple objects acting like a shell and manipulating the same collection. One instance will contain the element’s container and be a host for all of the notification events which consumers will be subscribed to, and other instances of the shell will handle disabled and delayed events. These extra shells reference the same container but instead of firing events which consumer handlers attached to, they will call its own handlers which either collect these events or discard them.

The ObservableCollection implementation is based on a Collection which implements functionality, and ObservableCollection implements notifications. The Collection class is implemented as a shell around the IList interface. It contains a reference to a container which exposes the IList interface and manipulates this container through it. One of the constructors of the Collection class takes List as a parameter and allows this list to be a container for that Collection. This creates a way to have multiple Collection instances to manipulate the same container, which perfectly serves our purpose.

Unfortunately, this ability is lost in the ObservableCollection implementation. Instead of assigning IList to be a container for the instance, it creates a copy of that List and uses that copy to store elements. This limitation prevents us from inheriting from the ObservableCollection class.

ObservableCollectionEx is based on a Collection class as well, and implements exactly the same methods and properties that ObservableCollection does.

In addition to these members, ObservableCollectionEx exposes two methods to create disabled or delayed notification shells around the container. Methods of the shell created by DisableNotifications() produce no notifications on either INotifyPropertyChanged or INotifyCollectionChanged.

Calls to the methods of the shell created by DelayNorifications() produce no notifications until this instance goes out of scope or IDisposable.Dispose() has been called on it.

How it works

Except for a few performance tricks, ObservableCollectionEx behaves exactly as the ObservableCollection class. It uses Collection to perform its operations, notifies consumers via INotifyPropertyChanged and INotifyCollectionChanged, and creates a copy of the List if you pass it to a constructor.

The differences starts when the DelayNotifications() or DisableNotifications() methods are called. This method creates a new instance of the ObservableCollectionEx object and passes its constructor a reference to the original ObservableCollectionEx object, and the Boolean parameter that specifies if notifications are disabled or postponed. This new instance will have the same interface as the original, the same element’s container, but none of the consumer handlers attached to the CollectionChanged event. So when methods of this instance are called and events are fired, none of these are going anywhere but to temporary storage.

Once updates are done, and either this instance goes out of scope or Dispose() has been called, all of the collected events are combined into one and fired on CollectionChanged and PropertyChanged of the original object notifying all of the consumers about changes.

Using the code

The easiest way to include this class into your project is by installing the Nuget package available at this link.

ObservableCollectionEx should be used exactly as ObservableCollection. It could be instantiated and used in place of ObservableCollection, or it could be derived from it. No special treatment is required.

In order to postpone notifications, it is recommended to use the using() directive:

ObservableCollectionEx<T> target = new ObservableCollectionEx<T>();
using (ObservableCollectionEx<T> iDelayed = target.DelayNotifications())
{
  iDelayed.Add(item0);
  iDelayed.Add(item0);
  iDelayed.Add(item0);
}

Due to the design of notification arguments, it is not possible to combine different operations together. For example, it is not possible to Add and Remove elements on the same delayed instance unless Dispose() has been called in between these calls. Calling Dispose() will fire previously collected events and reinitialize operation.

ObservableCollectionEx<T> target = new ObservableCollectionEx<T>();
using (ObservableCollectionEx<T> iDelayed = target.DelayNotifications())
{
    iDelayed.Add(item0);
    iDelayed.Add(item0);
}
using (ObservableCollectionEx<T> iDelayed = target.DelayNotifications())
{
    iDelayed.Remove(item0);
    iDelayed.Remove(item0);
}
using (ObservableCollectionEx<T> iDelayed = target.DelayNotifications())
{
    iDelayed.Add(item0);
    iDelayed.Add(item0); 
    iDelayed.Dispose();
    iDelayed.Remove(item0);
    iDelayed.Remove(item0);
}

Performance

In general, both ObservableCollection and ObservableCollectionEx provide comparable performance. Testing has been done using an array of 10,000 unique objects. Both ObservableCollection and ObservableCollectionEx where initialized with this array to pre allocate storage so it is not affecting timing results. Application has been run about dozen times to let JIT to optimize the executable before the test results were collected.

The test consisted of 10,000 Add, Replace, and Remove operations. Timing has been collected using the Stopwatch class and presented in milliseconds.

ObservableCollectionEx/ObservableCollectionEx.png

The value on the left represents the number of milliseconds it took to complete the test (Add, Replace, and Remove). The value on the bottom specifies the number of notification subscribers (handlers added to the CollectionChanged event).

As you can see from the graph, the performance of the interface with disabled notifications does not depend on the subscribers. Due to several performance enhancements, ObservableCollectionEx performs slightly better than ObservableCollection regardless of the number of subscribers but it obviously loses the Disabled interface once there is more than one subscriber.

The performance of ObservableCollectionEx when notifications are delayed is different compared to the results described above. Since notification is called only once, it saves some time but it requires some extra processing to unwind saved notifications. Time spent on notifications for ObservableCollection and ObservableCollectionEx are described by the following equitation:

ObservableCollection: overhead = (n * a) + (n * b)

ObservableCollectionEx: overhead = a + c + (n * b)

Where a is a constant overhead required to execute notification, n is number of changed elements, b is the cost of redrawing each individual element, and c the overhead required to execute delayed notification.

ObservableCollectionEx/DelayedPerformance.png

The value on the left represents the time required to complete notifications. The value on the bottom specifies the number of changed elements.

In these equations, values a and c are constants so the performance depends only on two elements: b – the time required to redraw each individual element, and n – the number of notified elements. As you know from calculus, b controls how steep the raise of the graph is. So when the time required to redraw each element (b) increases, these two lines meet sooner. It means it takes less changed elements to see performance benefits.

History

  • 09/05/2011 - Released.
  • 09/11/2011 - Fixed the PropertyChanged null reference.
  • 09/11/2011 - Fixed CollectionView incompatibility (big thanks to Fred who pointed it out).
  • 10/06/2011 - Added Nuget package at this link.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Eugene Sadovoi
Software Developer (Senior)
United States United States
Senior Software Engineer with over 20+ years of experience in variety of technologies, development tools and programming languages.

Microsoft Certified Specialist programming in C#, JavaScript, HTML, CSS

You may also be interested in...

Comments and Discussions

Discussions on this specific version of this article. Add your comments on how to improve this article here. These comments will not be visible on the final published version of this article.
 
GeneralMy vote of 5 Pin
mark merrens3-Apr-12 4:59
membermark merrens3-Apr-12 4:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.


Discussions posted for the Published version of this article. Posting a message here will take you to the publicly available article in order to continue your conversation in public.
 
Questionindex on NotifyCollectionChangedEventArgs Pin
Crosscourt27-Nov-13 23:11
memberCrosscourt27-Nov-13 23:11 
AnswerRe: index on NotifyCollectionChangedEventArgs Pin
Eugene Sadovoi3-Dec-13 9:00
memberEugene Sadovoi3-Dec-13 9:00 
QuestionBug in compare with ObservableCollection Pin
schulz300024-Sep-13 5:55
memberschulz300024-Sep-13 5:55 
AnswerRe: Bug in compare with ObservableCollection Pin
Eugene Sadovoi4-Oct-13 8:32
memberEugene Sadovoi4-Oct-13 8:32 
AnswerRe: Bug in compare with ObservableCollection Pin
Eugene Sadovoi24-Oct-13 4:01
memberEugene Sadovoi24-Oct-13 4:01 
QuestionNotifyCollectionChangedAction.Reset? Pin
Rohit Gadagkar11-Jul-13 1:11
memberRohit Gadagkar11-Jul-13 1:11 
AnswerRe: NotifyCollectionChangedAction.Reset? Pin
Eugene Sadovoi11-Jul-13 4:44
memberEugene Sadovoi11-Jul-13 4:44 
GeneralMy vote of 4 Pin
gorgias9916-Apr-13 22:21
membergorgias9916-Apr-13 22:21 
AnswerRe: My vote of 4 Pin
Eugene Sadovoi17-Apr-13 13:12
memberEugene Sadovoi17-Apr-13 13:12 
QuestionWhy is CollectionChanged protected? Pin
ses4j19-Feb-13 16:48
memberses4j19-Feb-13 16:48 
AnswerRe: Why is CollectionChanged protected? Pin
Eugene Sadovoi13-Mar-13 5:54
memberEugene Sadovoi13-Mar-13 5:54 
GeneralRe: Why is CollectionChanged protected? Pin
metbone20-Dec-13 9:09
membermetbone20-Dec-13 9:09 
AnswerRe: Why is CollectionChanged protected? Pin
Eugene Sadovoi24-Dec-13 7:44
memberEugene Sadovoi24-Dec-13 7:44 
GeneralRe: Why is CollectionChanged protected? Pin
JensMig4-Feb-15 20:05
memberJensMig4-Feb-15 20:05 
GeneralMy vote of 5 Pin
Thornik29-Jan-13 6:18
memberThornik29-Jan-13 6:18 
GeneralRe: My vote of 5 Pin
Eugene Sadovoi31-Jan-13 13:36
memberEugene Sadovoi31-Jan-13 13:36 
QuestionWhat a Pity That It Does NOT Support Silverlight Pin
Peter Lee8-Nov-12 19:33
memberPeter Lee8-Nov-12 19:33 
AnswerRe: What a Pity That It Does NOT Support Silverlight Pin
Eugene Sadovoi9-Nov-12 6:31
memberEugene Sadovoi9-Nov-12 6:31 
AnswerRe: What a Pity That It Does NOT Support Silverlight Pin
Eugene Sadovoi26-Jan-13 10:07
memberEugene Sadovoi26-Jan-13 10:07 
GeneralMy vote of 5 Pin
JF201520-May-12 19:52
memberJF201520-May-12 19:52 
GeneralRe: My vote of 5 Pin
Eugene Sadovoi21-May-12 4:44
memberEugene Sadovoi21-May-12 4:44 
GeneralMy vote of 5 Pin
Jani Giannoudis14-May-12 21:07
mvpJani Giannoudis14-May-12 21:07 
GeneralRe: My vote of 5 Pin
Eugene Sadovoi21-May-12 4:45
memberEugene Sadovoi21-May-12 4:45 
SuggestionSimple improvement needed to avoid design guidelines voilation Pin
Ashutosh Bhawasinka7-May-12 21:21
memberAshutosh Bhawasinka7-May-12 21:21 
QuestionRe: Simple improvement needed to avoid design guidelines voilation Pin
Eugene Sadovoi8-May-12 9:27
memberEugene Sadovoi8-May-12 9:27 
QuestionBecause of these reasons I’ve chosen to implement delays by inheriting from ObservableCollection. Pin
FatCatProgrammer3-May-12 5:45
memberFatCatProgrammer3-May-12 5:45 
QuestionRe: Because of these reasons I’ve chosen to implement delays by inheriting from ObservableCollection. Pin
Eugene Sadovoi4-May-12 9:17
memberEugene Sadovoi4-May-12 9:17 
AnswerRe: Because of these reasons I’ve chosen to implement delays by inheriting from ObservableCollection. Pin
Matthew Searles7-May-12 20:13
memberMatthew Searles7-May-12 20:13 
GeneralRe: Because of these reasons I’ve chosen to implement delays by inheriting from ObservableCollection. Pin
Eugene Sadovoi8-May-12 9:28
memberEugene Sadovoi8-May-12 9:28 
QuestionQuestion Pin
Sk8tz11-Apr-12 13:43
memberSk8tz11-Apr-12 13:43 
AnswerRe: Question Pin
Eugene Sadovoi11-Apr-12 14:53
memberEugene Sadovoi11-Apr-12 14:53 
QuestionMy (changed) vote of #5 Pin
BillWoodruff24-Sep-11 0:14
memberBillWoodruff24-Sep-11 0:14 
AnswerRe: My (changed) vote of #5 Pin
Eugene Sadovoi24-Sep-11 5:58
memberEugene Sadovoi24-Sep-11 5:58 
GeneralMy vote of 4 Pin
albertoleon22-Sep-11 1:01
memberalbertoleon22-Sep-11 1:01 
AnswerRe: My vote of 4 Pin
Eugene Sadovoi22-Sep-11 4:20
memberEugene Sadovoi22-Sep-11 4:20 
GeneralNaming guideline broken Pin
albertoleon22-Sep-11 4:34
memberalbertoleon22-Sep-11 4:34 
AnswerRe: Naming guideline broken Pin
Eugene Sadovoi22-Sep-11 4:48
memberEugene Sadovoi22-Sep-11 4:48 
GeneralRe: Naming guideline broken Pin
mark merrens3-Apr-12 4:58
membermark merrens3-Apr-12 4:58 
QuestionMy vote of 4.5 [modified] Pin
makaveli_000016-Sep-11 6:51
membermakaveli_000016-Sep-11 6:51 
AnswerRe: My vote of 4.5 Pin
Eugene Sadovoi16-Sep-11 7:00
memberEugene Sadovoi16-Sep-11 7:00 
SuggestionMore practice-oriented performance tests Pin
Jani Giannoudis15-Sep-11 5:34
memberJani Giannoudis15-Sep-11 5:34 
GeneralRe: More practice-oriented performance tests Pin
Eugene Sadovoi15-Sep-11 5:42
memberEugene Sadovoi15-Sep-11 5:42 
AnswerRe: More practice-oriented performance tests Pin
Jani Giannoudis15-Sep-11 5:47
memberJani Giannoudis15-Sep-11 5:47 
GeneralRe: More practice-oriented performance tests Pin
Eugene Sadovoi15-Sep-11 5:50
memberEugene Sadovoi15-Sep-11 5:50 
AnswerRe: More practice-oriented performance tests Pin
Jani Giannoudis15-Sep-11 20:09
memberJani Giannoudis15-Sep-11 20:09 
QuestionRe: More practice-oriented performance tests Pin
Eugene Sadovoi16-Sep-11 3:03
memberEugene Sadovoi16-Sep-11 3:03 
AnswerRe: More practice-oriented performance tests Pin
Jani Giannoudis16-Sep-11 3:50
memberJani Giannoudis16-Sep-11 3:50 
GeneralRe: More practice-oriented performance tests Pin
Eugene Sadovoi16-Sep-11 4:55
memberEugene Sadovoi16-Sep-11 4:55 
AnswerRe: More practice-oriented performance tests Pin
Jani Giannoudis16-Sep-11 5:20
memberJani Giannoudis16-Sep-11 5:20 
AnswerRe: More practice-oriented performance tests Pin
Eugene Sadovoi16-Sep-11 7:02
memberEugene Sadovoi16-Sep-11 7:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150731.1 | Last Updated 3 Apr 2012
Article Copyright 2011 by Eugene Sadovoi
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid