36

Was considering the System.Collections.ObjectModel ObservableCollection<T> class. This one is strange because

  • it has an Add Method which takes one item only. No AddRange or equivalent.
  • the Notification event arguments has a NewItems property, which is a IList (of objects.. not T)

My need here is to add a batch of objects to a collection and the listener also gets the batch as part of the notification. Am I missing something with ObservableCollection ? Is there another class that meets my spec?

Update: Don't want to roll my own as far as feasible. I'd have to build in add/remove/change etc.. a whole lot of stuff.


Related Q:
https://stackoverflow.com/questions/670577/observablecollection-doesnt-support-addrange-method-so-i-get-notified-for-each

Community
  • 1
  • 1
Gishu
  • 134,492
  • 47
  • 225
  • 308

10 Answers10

20

It seems that the INotifyCollectionChanged interface allows for updating when multiple items were added, so I'm not sure why ObservableCollection<T> doesn't have an AddRange. You could make an extension method for AddRange, but that would cause an event for every item that is added. If that isn't acceptable you should be able to inherit from ObservableCollection<T> as follows:

public class MyObservableCollection<T> : ObservableCollection<T>
{
    // matching constructors ...

    bool isInAddRange = false;

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        // intercept this when it gets called inside the AddRange method.
        if (!isInAddRange) 
            base.OnCollectionChanged(e);
    }


    public void AddRange(IEnumerable<T> items)
    {
         isInAddRange = true;
         foreach (T item in items)
            Add(item);
         isInAddRange = false;

         var e = new NotifyCollectionChangedEventArgs(
             NotifyCollectionChangedAction.Add,
             items.ToList());
         base.OnCollectionChanged(e);
    }
}
fryguybob
  • 4,390
  • 2
  • 28
  • 36
  • The code snippet needs some corrections.. there is no ToList() in IEnumerable and AddRange should take a ICollection to be consistent... Since I had to steam-roll through this temp setback to my grand plans of meeting my weekly target, posting my code sample.. a bit shorter. – Gishu Sep 14 '08 at 18:56
  • 2
    Gishu, the ToList() method is a LINQ extension method available on IEnumerable. – Brad Wilson Sep 14 '08 at 19:06
  • Got it... You need to set project settings to use .NET 3.5 and add the LINQ assembly reference and using directive to get it. – Gishu Sep 28 '08 at 12:19
  • i'm trying to do something pretty much identical.. even modified it to use your AddRange method for testing, yet, i still get "Item does not exist in collection" http://tiny.cc/iim24 – Sonic Soul Jul 21 '10 at 16:52
  • 6
    The code posted does not work if you use `AddRange` with more than one Item. The `CollectionChanged` event can't handle being raised manually with more than one item in the changed list - you get a `Range actions are not supported` exception – Rachel Sep 16 '11 at 19:30
6

Well the idea is same as that of fryguybob - kinda weird that ObservableCollection is kinda half-done. The event args for this thing do not even use Generics.. making me use an IList (that's so.. yesterday :) Tested Snippet follows...

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

namespace MyNamespace
{
    public class ObservableCollectionWithBatchUpdates<T> : ObservableCollection<T>
    {
        public void AddRange(ICollection<T> obNewItems)
        {
            IList<T> obAddedItems = new List<T>();
            foreach (T obItem in obNewItems)
            {
                Items.Add(obItem);
                obAddedItems.Add(obItem);
            }
            NotifyCollectionChangedEventArgs obEvtArgs = new NotifyCollectionChangedEventArgs(
               NotifyCollectionChangedAction.Add, 
               obAddedItems as System.Collections.IList);
            base.OnCollectionChanged(obEvtArgs);
        }

    }
}
Gishu
  • 134,492
  • 47
  • 225
  • 308
  • 4
    I tried this approach before. Unfortunately this won't work for WPF bindings, because notifications for several items are not supported. See [this bug](https://connect.microsoft.com/WPF/feedback/details/514922/range-actions-not-supported-in-collectionview) on MS Connect – Thomas Levesque Sep 30 '10 at 15:49
4

Not only is System.Collections.ObjectModel.Collection<T> a good bet, but in the help docs there's an example of how to override its various protected methods in order to get notification. (Scroll down to Example 2.)

Ryan Lundy
  • 204,559
  • 37
  • 180
  • 211
4

If you use any of the above implementations that send an add range command and bind the observablecolletion to a listview you will get this nasty error.

NotSupportedException
   at System.Windows.Data.ListCollectionView.ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs e)
   at System.Windows.Data.ListCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
   at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)

The implementation I have gone with uses the Reset event that is more evenly implemented around the WPF framework:

    public void AddRange(IEnumerable<T> collection)
    {
        foreach (var i in collection) Items.Add(i);
        OnPropertyChanged("Count");
        OnPropertyChanged("Item[]");
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
3

I have seen this kind of question many times, and I wonder why even Microsoft is promoting ObservableCollection everywhere where else there is a better collection already available thats..

BindingList<T>

Which allows you to turn off notifications and do bulk operations and then turn on the notifications.

Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • 1
    Very good point about `BindingList` but unfortunately it implements `IBindingList` instead of `IObservable`... The later is needed for WPF applications using mvvm. – RonnBlack Jun 01 '16 at 21:16
2

If you're wanting to inherit from a collection of some sort, you're probably better off inheriting from System.Collections.ObjectModel.Collection because it provides virtual methods for override. You'll have to shadow methods off of List if you go that route.

I'm not aware of any built-in collections that provide this functionality, though I'd welcome being corrected :)

David Mohundro
  • 11,922
  • 5
  • 40
  • 44
2

Another solution that is similar to the CollectionView pattern:

public class DeferableObservableCollection<T> : ObservableCollection<T>
{
    private int deferLevel;

    private class DeferHelper<T> : IDisposable
    {
        private DeferableObservableCollection<T> owningCollection;
        public DeferHelper(DeferableObservableCollection<T> owningCollection)
        {
            this.owningCollection = owningCollection;
        }

        public void Dispose()
        {
            owningCollection.EndDefer();
        }
    }

    private void EndDefer()
    {
        if (--deferLevel <= 0)
        {
            deferLevel = 0;
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }

    public IDisposable DeferNotifications()
    {
        deferLevel++;
        return new DeferHelper<T>(this);
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (deferLevel == 0) // Not in a defer just send events as normally
        {
            base.OnCollectionChanged(e);
        } // Else notify on EndDefer
    }
}
Mo0gles
  • 10,517
  • 2
  • 21
  • 15
1

Inherit from List<T> and override the Add() and AddRange() methods to raise an event?

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
0

Take a look at Observable collection with AddRange, RemoveRange and Replace range methods in both C# and VB.

In VB: INotifyCollectionChanging implementation.

Community
  • 1
  • 1
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
0

For fast adding you could use:

((List<Person>)this.Items).AddRange(NewItems);
Jérôme Verstrynge
  • 57,710
  • 92
  • 283
  • 453
Boris
  • 1