Bulk loading in ObservableCollection.

Today, in Code Project, a poster asked a question about the best way to insert multiple items in ObservableCollection as a bulk insert. The poster wondered why it was taking so long to add records doing an Add for each item in the collection. Now, in several of the .NET collections, there’s an AddRange method that allows you to add multiple items in “one hit”, so why isn’t there one in ObservableCollection? Well, part of the reason seems to be that this goes outside what you would use an ObservableCollection for. In other words, it’s not aimed at adding multiple items, it’s aimed at monitoring small changes.

So, what makes ObservableCollection unsuitable for large scale inserts? Well, the primary reason lies in the fact that an Add operation fires off a CollectionChanged event – it’s this event that is contributing to a large part of the slowdown. Fortunately, there is a way to get round this limitation, and it lies in the wonderful world of inheritance.

The following class is based on an ObservableCollection, and adds a new method (AddRange) that adds the items in quickly by suppressing the CollectionChanged event until the load has finished. (It doesn’t implement a RemoveRange method, but it would follow a similar pattern). Simple tests showed that this ran 3 times faster than the ordinary methods.

public class RangeObservableCollection<T> : ObservableCollection<T>
{
  private bool _suppressNotification = false;

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

  public void AddRange(IEnumerable<T> list)
  {
    if (list == null)
      throw new ArgumentNullException("list");

    _suppressNotification = true;

    foreach (T item in list)
    {
      Add(item);
    }
    _suppressNotification = false;
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  }
}

Anyway, I hope this helps you out.

18 thoughts on “Bulk loading in ObservableCollection.

  1. Collin

    if the OnCollectionChanged using a different NotifyCollectionChangedEventArgs you can still observe the collection changeing. Using the constuctor of only passing NotifyCollectionChangeAction.Reset will not allow listeners to observe changes.

    I suggest using

    NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list);
    OnCollectionChanged(args);

    And then for removal use .Remove

  2. Qwe

    My datagrid 50*4 is binding on ObservableCollection. Changes occur frequently and over and over.

    I used OnCollectionChanged, and saw a heavy load on the CPU. I tried use OnCollectionChanged from this topic, and saw a same heavy load on the CPU.
    I did not understand the reason. I tried use OnPropertyChanged, and heavy load on the CPU disappeared.

    To understand what is the reason I tried to modify the elements (not Clear and Add), but used the event OnCollectionChanged, which is called once, after all the changes. I get same – a heavy load on the CPU. So, I think, OnCollectionChanged – very slowly. Even if you do not use to redraw the elements, its use require much more CPU resourses…

  3. Pingback: Adding many entries to an Observable collection in a performance friendly way « Binary sculpting

  4. Pingback: Can I somehow temporarily disable WPF data binding changes? | PHP Developer Resource

  5. You should change implementation to use different constructor for this NotifyCollectionChangedEventArgs and provide a list of added items. In that way subscribers will be able to react accordingly without re-reading all list

  6. Fareed

    HI, Your tutorial Is day saver for me.
    But I’m still having problem.
    When I try to re-enter items to Series Collection ,so after calling it for 3 to four times it took more time.
    For example on calling AddRange method for first Time it took 1 sec.
    Then calling AddRange method for second time took 2 sec.
    And execution time get increased in incremental manner (i.e 1sec, 2 sec, 3sec etc).

  7. Beauty

    Still great, even after years (-:

    Thanks for you !!

    By the way: I extended the class by a RemoveRange() method.

    ———————————————————
    public void RemoveRange(IEnumerable list)
    {
    if (list== null)
    throw new ArgumentNullException(“list”);

    _suppressNotification = true;

    foreach (T item in list)
    {
    if (Contains(item) == true)
    {
    Remove(item);
    }
    }
    _suppressNotification = false;
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

  8. jody

    just adding a classical batch update to this
    public class RangeObservableCollection : ObservableCollection
    {
    private bool _suppressNotification = false;

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

    public void BatchUpdate()
    {
    _suppressNotification = true;
    }
    public void EndBatchUpdate()
    {
    _suppressNotification = false;
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
    public void AddRange(IEnumerable list)
    {
    if (list == null)
    throw new ArgumentNullException(“list”);

    var prevState = _suppressNotification;
    _suppressNotification = true;

    foreach (T item in list)
    {
    Add(item);
    }
    _suppressNotification = prevState;
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, null));
    }
    public void RemoveRange(IEnumerable list)
    {
    if (list == null)
    throw new ArgumentNullException(“list”);

    var prevState = _suppressNotification;
    _suppressNotification = true;

    foreach (T item in list)
    {
    Remove(item);
    }
    _suppressNotification = prevState;
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, list));
    }
    }

  9. Klus

    Why don’t you use Items.Add(item); It would not fire any notification and the _suppressNotification is not needed anymore.

Leave a comment