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.
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?
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.
34a1617b-b49f-4930-95e6-4645345a7cb2|1|5.0
Tags: