Skip to main content

ObservableCollection AddRange

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:

[csharp]
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);
}
}
}
[/csharp]

I created a scratch / test app to see if it works:

[xml]
<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>
[/xml]

[csharp]
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;
}
}
[/csharp]

[csharp]
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;
}
}
[/csharp]

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.

Comments

Popular posts from this blog

FileSystemWatcher With the BlockingCollection

While working with the FileSystemWatcher I found that if too many files were created the built in buffer will overflowed and files will be skipped.  After much research I found out about the Producer-Consumer Problem .  Then I found that .Net 4 has the BlockingCollection which helps solve the issue.  But how to use it with the FileSystemWatcher? On StackOverflow I found  Making PLINQ and BlockingCollection work together .  I'm not so interested in the PLINQ issue but this is a great example of using The BlockingCollection with FileSystemWatcher. [csharp] using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; namespace ConsoleApplication4 {     public class Program     {         private const string Folder = "C:\\Temp\\InputData";         static void Main(string[] args) {             var cts = new CancellationTokenSource();             foreach (var obj in Input(cts.Token))            

C# Spirograph Point Generators

Spirograph's  are cool.  See here and here . I put together three ways to generate points for a Spirograph, first using a Brute Force straight generate the points, second using a Parallel.For and third using LINQ.

Excel User Defined Functions with C# and ExcelDna

Adding user defined functions to Excel can be laborious, either you use VBA or COM. Why can't Microsoft make it easier to use Visual Studio and .NET within Excel? Enter ExcelDna . This example creates a static function in C# using Visual Studio. There is documentation that already describes this, what I am adding is: Use Visual Studio to create project Post Build Events to help create the final XLL file. NOTE: I hate Post Build Events. The ONLY exception is in this case where I am modifying the output of the final build. Post Build Events are Evil especially when used in a large development team. A better solutions is to create actual Build Scripts that do what I am about to do. You have been warned. First, create a new Class Library project. Use NuGet to add a reference to the Excel-DNA package. NuGet will also add ExcelDna.dna and ExcelDna.xll to your project. Rename them both to the name that you want your final output xll to be. In my case I renamed them to Scratch-ExcelDn