Damon Payne has a post AddRange for ObservableCollection in Silverlight 3. It is pretty short and sweet way to improve performance when batch adding data to an ObservableCollection.
He created a SmartCollection:
public class SmartCollection<T> : ObservableCollection<T>
{
public SmartCollection()
{
_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();
try
{
foreach (var i in items)
base.InsertItem(Count, i);
}
finally
{
this.ResumeCollectionChangeNotification();
var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
this.OnCollectionChanged(arg);
}
}
}
I created a scratch / test app to see if it works:
<UserControl
x:Class="SilverlightApplication10.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Orientation="Horizontal">
<StackPanel Margin="5">
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock x:Name="SlowText" Height="50" Width="100"/>
<Button x:Name="SlowButton" Content="Slow" Click="SlowButtonClick"/>
</StackPanel>
<my:DataGrid x:Name="SlowDataView"
Height="300"
AutoGenerateColumns="True"
RowBackground="Cornsilk"
AlternatingRowBackground="LightGray"/>
</StackPanel>
<StackPanel Margin="5">
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock x:Name="FastText" Height="50" Width="100"/>
<Button x:Name="FastButton" Content="Fast" Click="FastButtonClick"/>
</StackPanel>
<my:DataGrid x:Name="FastDataView"
Height="300"
AutoGenerateColumns="True"
RowBackground="Cornsilk"
AlternatingRowBackground="LightGray"/>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
_slowCollection = new ObservableCollection<MyClass>();
SlowDataView.ItemsSource = _slowCollection;
_fastCollection = new SmartCollection<MyClass>();
FastDataView.ItemsSource = _fastCollection;
}
#region Slow
private readonly ObservableCollection<MyClass> _slowCollection;
private void SlowButtonClick(object sender, RoutedEventArgs e)
{
var timeSpan = Time(() => { for (int i = 0; i < 25000; i++) _slowCollection.Add(MyClass.BuildRandom()); });
Dispatcher.BeginInvoke(() => this.SlowText.Text = timeSpan.ToString());
}
#endregion
#region Fast
private readonly SmartCollection<MyClass> _fastCollection;
private void FastButtonClick(object sender, RoutedEventArgs e)
{
var timeSpan = Time(() =>
{
var list = new List<MyClass>();
for (int i = 0; i < 25000; i++) list.Add(MyClass.BuildRandom());
_fastCollection.AddRange(list);
});
Dispatcher.BeginInvoke(() => this.FastText.Text = timeSpan.ToString());
}
#endregion
private static TimeSpan Time(Action a)
{
var start = DateTime.Now;
a();
var end = DateTime.Now;
return end - start;
}
}
internal class MyClass : INotifyPropertyChanged
{
private static Random _random;
#region Id
private string _id;
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged("Id");
}
}
}
#endregion
#region SomeRandomNumber
private int _someRandomNumber;
public int SomeRandomNumber
{
get { return _someRandomNumber; }
set
{
if (value != _someRandomNumber)
{
_someRandomNumber = value;
NotifyPropertyChanged("SomeRandomNumber");
}
}
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public static MyClass BuildRandom()
{
if (_random == null)
_random = new Random((int)DateTime.Now.Ticks);
var myclass = new MyClass
{
Id = Guid.NewGuid().ToString("N"),
SomeRandomNumber = _random.Next(1, 100)
};
return myclass;
}
}
Running the code shows that indeed the AddRange works well to speed up inserts. I was getting about 15 to 25 seconds on the Slow Data and from 3 to 0.09 seconds on the Fast Data. It was interesting to note that if the Slow button was pressed a second time it actually took longer than the first click, but the Fast button had the opposite affect.