AddRange for ObservableCollection in Silverlight 3

by Administrator 4. March 2010 01:43

It’s great that the built-in controls in Silverlight understand INotifyCollectionChanged, and that ObservableCollection<T> ships with Silverlight.  However, when adding large data sets to ObservableCollection<T> the performance  can be brutal due to the fact that ObservableCollection<T> fires an event with every operation on the collection.  Most of the time this is what you want however some bulk operations like AddRange would be nice.  Let’s take a look at a way to add AddRange() to ObservbleCollection<T> using a technique that vastly increases performance.

Example UserControl

We’ll use a simple Silverlight root visual with a DataGrid on it to demonstrate this technique. Note that we are using a new class called SmartCollection<T> and an Item class with some randomly generated field values.

namespace HandWaver.AG.OCAddRange
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            _collection = new SmartCollection<Item>();
            DasGrid.ItemsSource = _collection;            
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            Dispatcher.BeginInvoke(() => BuildItems());
        }

        TimeSpan Time(Action a)
        {
            var start = DateTime.Now;
            a();
            var end = DateTime.Now;
            return end - start;
        }

        SmartCollection<Item> _collection;
        
        private void BuildItems()
        {
            Action slow = () =>
            {
                for (int i = 0; i < 25000; ++i)
                {
                    _collection.Add(new Item());
                }
            };

Using this technique and displaying the time it took to add these items to the data grid gives me this result.  Note that Stopwatch is not present in Silverlight 3.

time0

Nearly 10 seconds to add 25,000 items to the DataGrid even though most of them are not visible.  Let’s see what SmartCollection<T> can do with slightly different code:

Action fast = () =>
{                
    var sourceList = new List<Item>();
    for (int i = 0; i < 25000; ++i)
    {
        sourceList.Add(new Item());
    }
    
    _collection.AddRange(sourceList);                
};

var time =
    //Time(slow);
    Time(fast);

So we’ve built a source collection and used the AddRange implementation.  How does it do?

time1

That’s a pretty significant speed up! Let’s go ahead and look at the code for SmartCollection<T> now.

using System;
using System.Linq;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace HandWaver.AG.PresentationModel
{
    public class SmartCollection<T> : ObservableCollection<T>
    {
        public SmartCollection()
            : base()
        {
            _suspendCollectionChangeNotification = false;
        }


        bool _suspendCollectionChangeNotification;

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

        public void SuspendCollectionChangeNotification()
        {
            _suspendCollectionChangeNotification = true;
        }

        public void ResumeCollectionChangeNotification()
        {
            _suspendCollectionChangeNotification = false;
        }


        public void AddRange(IEnumerable<T> items)
        {
            this.SuspendCollectionChangeNotification();
            int index = base.Count;
            try
            {
                foreach (var i in items)
                {
                    base.InsertItem(base.Count, i);
                }
            }
            finally
            {
                this.ResumeCollectionChangeNotification();
                var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                this.OnCollectionChanged(arg);
            }
        }

    }

}

Note that we could do an AddRange implementation via an extension method but we would not gain the performance improvements since events would still fire for every operation.  By extending ObservableCollection<T> we gain the ability to create our own AddRange method.  The AddRange method sets a flag that temporarily suppresses the collection changed notification for ObservableCollection<T>.   At the end it uses NotifyCollectionChangedAction.Reset to indicate that a significant change was made to the collection.  The UI rebuilds itself in a fraction of the time it took to add the items one by one.  You can use this technique to gain huge performance improvements for your Silverlight 3 apps dealing with large data sets.

Tags:

Comments (3) -

Arnoud van Bers
Arnoud van Bers
3/4/2010 1:10:08 PM #

Great Solution!
Made a reference to your solution on the silverlight forum:
forums.silverlight.net/.../373382.aspx#373382

Reply

Raoul
Raoul
6/8/2010 3:35:59 PM #

Used this technique for sorting a huge ObservableCollection behind a WPF listview. Works great. Thanks!

Reply

Simon Draht
Simon Draht Germany
2/2/2011 7:58:39 AM #

Thank you, this was exactly what I needed.
Seems what you need now is a good spamfilter ;)

Reply

Pingbacks and trackbacks (1)+

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading


About the author

Damon Payne is a Microsoft MVP specializing in Smart Client solution architecture. 

INETA Community Speakers Program

Month List

Page List

flickr photostream