Description
Update 10/04/2018
@ianhays and I discussed this and we agree to add this 6 APIs for now:
// Adds a range to the end of the collection.
// Raises CollectionChanged (NotifyCollectionChangedAction.Add)
public void AddRange(IEnumerable<T> collection) => InsertItemsRange(0, collection);
// Inserts a range
// Raises CollectionChanged (NotifyCollectionChangedAction.Add)
public void InsertRange(int index, IEnumerable<T> collection) => InsertItemsRange(index, collection);
// Removes a range.
// Raises CollectionChanged (NotifyCollectionChangedAction.Remove)
public void RemoveRange(int index, int count) => RemoveItemsRange(index, count);
// Will allow to replace a range with fewer, equal, or more items.
// Raises CollectionChanged (NotifyCollectionChangedAction.Replace)
public void ReplaceRange(int index, int count, IEnumerable<T> collection)
{
RemoveItemsRange(index, count);
InsertItemsRange(index, collection);
}
#region virtual methods
protected virtual void InsertItemsRange(int index, IEnumerable<T> collection);
protected virtual void RemoveItemsRange(int index, int count);
#endregion
As those are the most commonly used across collection types and the Predicate
ones can be achieved through Linq and seem like edge cases.
To answer @terrajobst questions:
Should the methods be virtual? If no, why not? If yes, how does eventing work and how do derived types work?
Yes, we would like to introduce 2 protected virtual methods to stick with the current pattern that we follow with other Insert/Remove apis to give people hability to add their custom removals (like filtering items on a certain condition).
Should some of these methods be pushed down to Collection?
Yes, and then ObservableCollection
could just call the base implementation and then trigger the necessary events.
Let's keep the final speclet at the top for easier search
Speclet (Updated 9/23/2016)
Scope
Modernize Collection<T>
and ObservableCollection<T>
by allowing them to handle operations against multiple items simultaneously.
Rationale
The ObservableCollection
is a critical collection when it comes to XAML-based development, though it can also be useful when building API client libraries as well. Because it implements INotifyPropertyChanged
and INotifyCollectionChanged
, nearly every XAML app in existence uses some form of this collection to bind a set of objects against UI.
However, this class has some shortcomings. Namely, it cannot currently handle adding or removing multiple objects in a single call. Because of that, it also cannot manipulate the collection in such a way that the PropertyChanged
events are raised at the very end of the operation.
Consider the following situation:
- You have a XAML app that accesses an API.
- That API call returns 25 objects that need to be bound to the UI.
- In order to get the data displayed into the UI, you likely have to cycle through the results, and add them one at a time to the ObservableCollection.
- This has the side-effect of firing the
CollectionChanged
event 25 times. If you are also using that event to do other processing on incoming items, then those events are firing 25 times too. This can get very expensive, very quickly. - Additionally, that event will have
ChangedItems
Lists that will only ever have 0 or 1 objects in them. That is... not ideal.
This behavior is unnecessary, especially considering that NotifyCollectionChangedEventArgs
already has the components necessary to handle firing the event once for multiple items, but that capability is presently not being used at all.
Implementing this properly would allow for better performance in these types of apps, and would negate the need for the plethora of replacements out there (here, here, and here, for example).
Usage
Given the above scenario as an example, usage would look like this pseudocode:
var observable = new ObservableCollection<SomeObject>();
var client = new HttpClient();
var result = client.GetStringAsync("http://someapi.com/someobject");
var results = JsonConvert.DeserializeObject<SomeObject>(result);
observable.AddRange(results);
Implementation
This is not the complete implementation, because other *Range
functionality would need to be implemented as well. You can see the start of this work in PR dotnet/corefx#10751
// Adds a range to the end of the collection.
// Raises CollectionChanged (NotifyCollectionChangedAction.Add)
public void AddRange(IEnumerable<T> collection)
// Inserts a range
// Raises CollectionChanged (NotifyCollectionChangedAction.Add)
public void InsertRange(int index, IEnumerable<T> collection);
// Removes a range.
// Raises CollectionChanged (NotifyCollectionChangedAction.Remove)
public void RemoveRange(int index, int count);
// Will allow to replace a range with fewer, equal, or more items.
// Raises CollectionChanged (NotifyCollectionChangedAction.Replace)
public void ReplaceRange(int index, int count, IEnumerable<T> collection);
// Removes any item that matches the search criteria.
// Raises CollectionChanged (NotifyCollectionChangedAction.Remove)
// RWM: Excluded for now, will see if possible to add back in after implementation and testing.
// public int RemoveAll(Predicate<T> match);
Obstacles
Doing this properly, and having the methods intuitively named, could potentially have the side effect of breaking existing classes that inherit from ObservableCollection
to solve this problem. A good way to test this would be to make the change, compile something like Template10 against this new assembly, and see if it breaks.
So the ObservableCollection
is one of the cornerstones of software development, not just in Windows, but on the web. One issue that comes up constantly is that, while the OnCollectionChanged
event has a structure and constructors that support signaling the change for multiple items being added, the ObservableCollection
does not have a method to support this.
If you look at the web as an example, Knockout has a way to be able to add multiple items to the collection, but not signal the change until the very end. The ObservableCollection
needs the same functionality, but does not have it.
If you look at other extension methods to solve this problem, like the one in Template10, they let you add multiple items, but do not solve the signaling problem. That's because the ObservableCollection.InsertItem()
method overrides Collection.InsertItem()
, and all of the other methods are private. So the only way to fix this properly is in the ObservableCollection
itself.
I'm proposing an "AddRange" function that accepts an existing collection as input, optionally clears the collection before adding, and then throws the OnCollectionChanging
event AFTER all the objects have been added. I have already implemented this in a PR dotnet/corefx#10751 so you can see what the implementation would look like.
I look forward to your feedback. Thanks!