Which .Net collection for adding multiple objects at once and getting notified?
Asked Answered
P

10

37

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://mcmap.net/q/134975/-observablecollection-doesn-39-t-support-addrange-method-so-i-get-notified-for-each-item-added-besides-what-about-inotifycollectionchanging

Pithead answered 11/9, 2008 at 16:15 Comment(1)
Gishu, careful, if you bind to a listview most of the implementations here will blow up.Frangipane
D
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);
    }
}
Dripdry answered 11/9, 2008 at 16:43 Comment(5)
The code snippet needs some corrections.. there is no ToList() in IEnumerable and AddRange should take a ICollection<T> 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.Pithead
Gishu, the ToList() method is a LINQ extension method available on IEnumerable.Bloodyminded
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.Pithead
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" tiny.cc/iim24Squamulose
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 exceptionGrafton
P
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);
        }

    }
}
Pithead answered 14/9, 2008 at 19:3 Comment(1)
I tried this approach before. Unfortunately this won't work for WPF bindings, because notifications for several items are not supported. See this bug on MS ConnectGuberniya
C
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.)

Cogan answered 12/9, 2008 at 3:13 Comment(0)
F
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));
    }
Frangipane answered 12/5, 2009 at 4:54 Comment(3)
you could go with an add for every item, but your UI will crawl to a haltFrangipane
Actually, no it won't - WPF UI renders in batches, as a postmessage. This function will terminate before the UI is checked for a refresh.Noncontributory
The same exception occurred here, anyone help us to resolve thisHebbel
B
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.

Buckles answered 30/9, 2010 at 9:58 Comment(1)
Very good point about BindingList<T> but unfortunately it implements IBindingList<T> instead of IObservable<T>... The later is needed for WPF applications using mvvm.Tenno
J
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 :)

Jemena answered 11/9, 2008 at 16:21 Comment(0)
O
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
    }
}
Oxime answered 12/1, 2012 at 15:30 Comment(0)
P
1

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

Perquisite answered 11/9, 2008 at 16:19 Comment(0)
A
0

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

In VB: INotifyCollectionChanging implementation.

Anagnos answered 14/7, 2009 at 3:25 Comment(0)
C
0

For fast adding you could use:

((List<Person>)this.Items).AddRange(NewItems);
Colburn answered 24/11, 2009 at 14:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.