ObservableCollection Unterstützt die AddRange-Methode nicht, daher werde ich für jedes hinzugefügte Element benachrichtigt, außer was ist mit INotifyCollectionChanging?

Ich möchte in der Lage sein, einen Bereich hinzuzufügen und für die gesamte Masse aktualisiert werden.

Ich möchte auch die Aktion abbrechen können, bevor sie fertig ist (dh die Sammlung ändert sich neben der “Änderung”).


Verwandte Themen Q.Net-Auflistung zum gleichzeitigen Hinzufügen mehrerer Objekte und zur Benachrichtigung.

Bitte beachten Sie die aktualisierte und optimierte Version C # 7 . Ich wollte die VB.NET-Version nicht entfernen, also habe ich sie einfach in einer separaten Antwort gepostet.

Gehe zur aktualisierten Version

Scheint, es wird nicht unterstützt, ich selbst implementiert, FYI, hoffe es hilfreich zu sein:

Ich habe die VB-Version aktualisiert und von nun an triggers sie ein Ereignis aus, bevor die Sammlung DataGrid , so dass Sie bereuen können (nützlich bei Verwendung mit DataGrid , ListView und vielen anderen, dass Sie dem Benutzer eine “Sind Sie sicher” ListView zeigen können) Die aktualisierte VB-Version befindet sich am Ende dieser Nachricht .

Bitte akzeptieren Sie meine Entschuldigung, dass der Bildschirm zu eng ist, um meinen Code zu enthalten, ich mag es auch nicht.

VB.NET:

 Imports System.Collections.Specialized Namespace System.Collections.ObjectModel '''  ''' Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. '''  '''  Public Class ObservableRangeCollection(Of T) : Inherits System.Collections.ObjectModel.ObservableCollection(Of T) '''  ''' Adds the elements of the specified collection to the end of the ObservableCollection(Of T). '''  Public Sub AddRange(ByVal collection As IEnumerable(Of T)) For Each i In collection Items.Add(i) Next OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)) End Sub '''  ''' Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). '''  Public Sub RemoveRange(ByVal collection As IEnumerable(Of T)) For Each i In collection Items.Remove(i) Next OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)) End Sub '''  ''' Clears the current collection and replaces it with the specified item. '''  Public Sub Replace(ByVal item As T) ReplaceRange(New T() {item}) End Sub '''  ''' Clears the current collection and replaces it with the specified collection. '''  Public Sub ReplaceRange(ByVal collection As IEnumerable(Of T)) Dim old = Items.ToList Items.Clear() For Each i In collection Items.Add(i) Next OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)) End Sub '''  ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. '''  '''  Public Sub New() MyBase.New() End Sub '''  ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. '''  ''' collection: The collection from which the elements are copied. ''' The collection parameter cannot be null. Public Sub New(ByVal collection As IEnumerable(Of T)) MyBase.New(collection) End Sub End Class End Namespace 

C #:

 using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; ///  /// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. ///  ///  public class ObservableRangeCollection : ObservableCollection { ///  /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). ///  public void AddRange(IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); foreach (var i in collection) Items.Add(i); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } ///  /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). ///  public void RemoveRange(IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); foreach (var i in collection) Items.Remove(i); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } ///  /// Clears the current collection and replaces it with the specified item. ///  public void Replace(T item) { ReplaceRange(new T[] { item }); } ///  /// Clears the current collection and replaces it with the specified collection. ///  public void ReplaceRange(IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); Items.Clear(); foreach (var i in collection) Items.Add(i); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } ///  /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. ///  public ObservableRangeCollection() : base() { } ///  /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. ///  /// collection: The collection from which the elements are copied. /// The collection parameter cannot be null. public ObservableRangeCollection(IEnumerable collection) : base(collection) { } } 

Update – Observable Bereich Sammlung mit Sammlung ändern Benachrichtigung

 Imports System.Collections.Specialized Imports System.ComponentModel Imports System.Collections.ObjectModel Public Class ObservableRangeCollection(Of T) : Inherits ObservableCollection(Of T) : Implements INotifyCollectionChanging(Of T) '''  ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. '''  '''  Public Sub New() MyBase.New() End Sub '''  ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. '''  ''' collection: The collection from which the elements are copied. ''' The collection parameter cannot be null. Public Sub New(ByVal collection As IEnumerable(Of T)) MyBase.New(collection) End Sub '''  ''' Adds the elements of the specified collection to the end of the ObservableCollection(Of T). '''  Public Sub AddRange(ByVal collection As IEnumerable(Of T)) Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Add, collection) OnCollectionChanging(ce) If ce.Cancel Then Exit Sub Dim index = Items.Count - 1 For Each i In collection Items.Add(i) Next OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection, index)) End Sub '''  ''' Inserts the collection at specified index. '''  Public Sub InsertRange(ByVal index As Integer, ByVal Collection As IEnumerable(Of T)) Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Add, Collection) OnCollectionChanging(ce) If ce.Cancel Then Exit Sub For Each i In Collection Items.Insert(index, i) Next OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)) End Sub '''  ''' Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). '''  Public Sub RemoveRange(ByVal collection As IEnumerable(Of T)) Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Remove, collection) OnCollectionChanging(ce) If ce.Cancel Then Exit Sub For Each i In collection Items.Remove(i) Next OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)) End Sub '''  ''' Clears the current collection and replaces it with the specified item. '''  Public Sub Replace(ByVal item As T) ReplaceRange(New T() {item}) End Sub '''  ''' Clears the current collection and replaces it with the specified collection. '''  Public Sub ReplaceRange(ByVal collection As IEnumerable(Of T)) Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Replace, Items) OnCollectionChanging(ce) If ce.Cancel Then Exit Sub Items.Clear() For Each i In collection Items.Add(i) Next OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)) End Sub Protected Overrides Sub ClearItems() Dim e As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Reset, Items) OnCollectionChanging(e) If e.Cancel Then Exit Sub MyBase.ClearItems() End Sub Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As T) Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Add, item) OnCollectionChanging(ce) If ce.Cancel Then Exit Sub MyBase.InsertItem(index, item) End Sub Protected Overrides Sub MoveItem(ByVal oldIndex As Integer, ByVal newIndex As Integer) Dim ce As New NotifyCollectionChangingEventArgs(Of T)() OnCollectionChanging(ce) If ce.Cancel Then Exit Sub MyBase.MoveItem(oldIndex, newIndex) End Sub Protected Overrides Sub RemoveItem(ByVal index As Integer) Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Remove, Items(index)) OnCollectionChanging(ce) If ce.Cancel Then Exit Sub MyBase.RemoveItem(index) End Sub Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As T) Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Replace, Items(index)) OnCollectionChanging(ce) If ce.Cancel Then Exit Sub MyBase.SetItem(index, item) End Sub Protected Overrides Sub OnCollectionChanged(ByVal e As Specialized.NotifyCollectionChangedEventArgs) If e.NewItems IsNot Nothing Then For Each i As T In e.NewItems If TypeOf i Is INotifyPropertyChanged Then AddHandler DirectCast(i, INotifyPropertyChanged).PropertyChanged, AddressOf Item_PropertyChanged Next End If MyBase.OnCollectionChanged(e) End Sub Private Sub Item_PropertyChanged(ByVal sender As T, ByVal e As ComponentModel.PropertyChangedEventArgs) OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, sender, IndexOf(sender))) End Sub Public Event CollectionChanging(ByVal sender As Object, ByVal e As NotifyCollectionChangingEventArgs(Of T)) Implements INotifyCollectionChanging(Of T).CollectionChanging Protected Overridable Sub OnCollectionChanging(ByVal e As NotifyCollectionChangingEventArgs(Of T)) RaiseEvent CollectionChanging(Me, e) End Sub End Class Public Interface INotifyCollectionChanging(Of T) Event CollectionChanging(ByVal sender As Object, ByVal e As NotifyCollectionChangingEventArgs(Of T)) End Interface Public Class NotifyCollectionChangingEventArgs(Of T) : Inherits CancelEventArgs Public Sub New() m_Action = NotifyCollectionChangedAction.Move m_Items = New T() {} End Sub Public Sub New(ByVal action As NotifyCollectionChangedAction, ByVal item As T) m_Action = action m_Items = New T() {item} End Sub Public Sub New(ByVal action As NotifyCollectionChangedAction, ByVal items As IEnumerable(Of T)) m_Action = action m_Items = items End Sub Private m_Action As NotifyCollectionChangedAction Public ReadOnly Property Action() As NotifyCollectionChangedAction Get Return m_Action End Get End Property Private m_Items As IList Public ReadOnly Property Items() As IEnumerable(Of T) Get Return m_Items End Get End Property End Class 

Ich denke AddRange ist besser so implementiert:

 public void AddRange(IEnumerable collection) { foreach (var i in collection) Items.Add(i); OnCollectionChanged( new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } 

Es speichert Ihnen eine Listenkopie. Wenn Sie Micro-Optimization durchführen möchten, können Sie auch bis zu N Elemente hinzufügen und wenn mehr als N Elemente hinzugefügt werden, ein Reset durchführen.

Der OnPropertyChanged("Count") für OnPropertyChanged("Count") und OnPropertyChanged("Item[]") ruft auf, um sich wie in ObservableCollection zu verhalten. Beachten Sie, dass ich nicht weiß, was die Konsequenzen sind, wenn Sie nicht stören!

Hier ist eine Testmethode, die zeigt, dass es zwei PropertyChange-Ereignisse für jedes Add in einer normalen beobachtbaren Auflistung gibt. Eine für "Count" und eine für "Item[]" .

 [TestMethod] public void TestAddSinglesInOldObsevableCollection() { int colChangedEvents = 0; int propChangedEvents = 0; var collection = new ObservableCollection(); collection.CollectionChanged += (sender, e) => { colChangedEvents++; }; (collection as INotifyPropertyChanged).PropertyChanged += (sender, e) => { propChangedEvents++; }; collection.Add(new object()); collection.Add(new object()); collection.Add(new object()); Assert.AreEqual(3, colChangedEvents); Assert.AreEqual(6, propChangedEvents); } 

@Shimmy, tausche den Standard für deine Sammlung und ändere ihn in einen Add-Bereich und du bekommst null PropertyChanges. Beachten Sie, dass die Änderung der Sammlung funktioniert, aber nicht genau das tun, was ObservableCollection tut. So sieht der Test für Shimmy Collection folgendermaßen aus:

 [TestMethod] public void TestShimmyAddRange() { int colChangedEvents = 0; int propChangedEvents = 0; var collection = new ShimmyObservableCollection(); collection.CollectionChanged += (sender, e) => { colChangedEvents++; }; (collection as INotifyPropertyChanged).PropertyChanged += (sender, e) => { propChangedEvents++; }; collection.AddRange(new[]{ new object(), new object(), new object(), new object()}); //4 objects at once Assert.AreEqual(1, colChangedEvents); //great, just one! Assert.AreEqual(2, propChangedEvents); //fails, no events :( } 

FYI hier ist Code von InsertItem (auch von Add) aus ObservableCollection:

 protected override void InsertItem(int index, T item) { base.CheckReentrancy(); base.InsertItem(index, item); base.OnPropertyChanged("Count"); base.OnPropertyChanged("Item[]"); base.OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index); } 

Bitte bewerten Sie zunächst die API-Anfrage im .NET Repo und kommentieren Sie diese.

Hier ist meine optimierte Version der ObservableRangeCollection (optimierte Version von James Montemagnos).

Es funktioniert sehr schnell und soll vorhandene Elemente nach Möglichkeit wiederverwenden und unnötige Ereignisse vermeiden oder sie, falls möglich, zu einem Stapel verarbeiten. Die ReplaceRange Methode ersetzt / entfernt / fügt die erforderlichen Elemente durch die entsprechenden Indizes hinzu und gruppiert die möglichen Ereignisse.

Getestet auf der Xamarin.Forms-Benutzeroberfläche mit großartigen Ergebnissen für sehr häufige Updates der großen Sammlung (5-7 Updates pro Sekunde).

Hinweis: Da WPF nicht daran gewöhnt ist, mit Bereichsoperationen zu arbeiten, wird eine NotSupportedException , wenn ObservableRangeCollection von unten in der WPF-UI-bezogenen Arbeit verwendet wird, z. B. durch Bindung an eine ListBox usw. (Sie können weiterhin die ObservableRangeCollection wenn nicht an UI gebunden).
Sie können jedoch die WpfObservableRangeCollection .
Die wirkliche Lösung wäre das Erstellen einer CollectionView , die mit Bereichsoperationen umgehen kann, aber ich hatte immer noch nicht die Zeit, dies zu implementieren.

RAW-Code – Öffnen Sie Raw, dann drücken Sie Strg + A , um alle auszuwählen, und dann Strg + C, um zu kopieren.

 // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; namespace System.Collections.ObjectModel { ///  /// Implementation of a dynamic data collection based on generic Collection<T>, /// implementing INotifyCollectionChanged to notify listeners /// when items get added, removed or the whole list is refreshed. ///  public class ObservableRangeCollection : ObservableCollection { //------------------------------------------------------ // // Private Fields // //------------------------------------------------------ #region Private Fields [NonSerialized] private DeferredEventsCollection _deferredEvents; #endregion Private Fields //------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors ///  /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity. ///  public ObservableRangeCollection() { } ///  /// Initializes a new instance of the ObservableCollection class that contains /// elements copied from the specified collection and has sufficient capacity /// to accommodate the number of elements copied. ///  /// The collection whose elements are copied to the new list. ///  /// The elements are copied onto the ObservableCollection in the /// same order they are read by the enumerator of the collection. ///  ///  collection is a null reference  public ObservableRangeCollection(IEnumerable collection) : base(collection) { } ///  /// Initializes a new instance of the ObservableCollection class /// that contains elements copied from the specified list ///  /// The list whose elements are copied to the new list. ///  /// The elements are copied onto the ObservableCollection in the /// same order they are read by the enumerator of the list. ///  ///  list is a null reference  public ObservableRangeCollection(List list) : base(list) { } #endregion Constructors //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods ///  /// Adds the elements of the specified collection to the end of the . ///  ///  /// The collection whose elements should be added to the end of the . /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. ///  ///  is null. public void AddRange(IEnumerable collection) { InsertRange(Count, collection); } ///  /// Inserts the elements of a collection into the  at the specified index. ///  /// The zero-based index at which the new elements should be inserted. /// The collection whose elements should be inserted into the List. /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. ///  is null. ///  is not in the collection range. public void InsertRange(int index, IEnumerable collection) { if (collection == null) throw new ArgumentNullException(nameof(collection)); if (index < 0) throw new ArgumentOutOfRangeException(nameof(index)); if (index > Count) throw new ArgumentOutOfRangeException(nameof(index)); if (collection is ICollection countable) { if (countable.Count == 0) { return; } } else if (!ContainsAny(collection)) { return; } CheckReentrancy(); //expand the following couple of lines when adding more constructors. var target = (List)Items; target.InsertRange(index, collection); OnEssentialPropertiesChanged(); if (!(collection is IList list)) list = new List(collection); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, index)); } ///  /// Removes the first occurence of each item in the specified collection from the . ///  /// The items to remove. ///  is null. public void RemoveRange(IEnumerable collection) { if (collection == null) throw new ArgumentNullException(nameof(collection)); if (Count == 0) { return; } else if (collection is ICollection countable) { if (countable.Count == 0) return; else if (countable.Count == 1) using (IEnumerator enumerator = countable.GetEnumerator()) { enumerator.MoveNext(); Remove(enumerator.Current); return; } } else if (!(ContainsAny(collection))) { return; } CheckReentrancy(); var clusters = new Dictionary>(); var lastIndex = -1; List lastCluster = null; foreach (T item in collection) { var index = IndexOf(item); if (index < 0) { continue; } Items.RemoveAt(index); if (lastIndex == index && lastCluster != null) { lastCluster.Add(item); } else { clusters[lastIndex = index] = lastCluster = new List { item }; } } OnEssentialPropertiesChanged(); if (Count == 0) OnCollectionReset(); else foreach (KeyValuePair> cluster in clusters) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster.Value, cluster.Key)); } ///  /// Iterates over the collection and removes all items that satisfy the specified match. ///  /// The complexity is O(n). ///  /// Returns the number of elements that where  ///  is null. public int RemoveAll(Predicate match) { return RemoveAll(0, Count, match); } ///  /// Iterates over the specified range within the collection and removes all items that satisfy the specified match. ///  /// The complexity is O(n). /// The index of where to start performing the search. /// The number of items to iterate on. ///  /// Returns the number of elements that where  ///  is out of range. ///  is out of range. ///  is null. public int RemoveAll(int index, int count, Predicate match) { if (index < 0) throw new ArgumentOutOfRangeException(nameof(index)); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (index + count > Count) throw new ArgumentOutOfRangeException(nameof(index)); if (match == null) throw new ArgumentNullException(nameof(match)); if (Count == 0) return 0; List cluster = null; var clusterIndex = -1; var removedCount = 0; using (BlockReentrancy()) using (DeferEvents()) { for (var i = 0; i < count; i++, index++) { T item = Items[index]; if (match(item)) { Items.RemoveAt(index); removedCount++; if (clusterIndex == index) { Debug.Assert(cluster != null); cluster.Add(item); } else { cluster = new List { item }; clusterIndex = index; } index--; } else if (clusterIndex > -1) { OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex)); clusterIndex = -1; cluster = null; } } if (clusterIndex > -1) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex)); } if (removedCount > 0) OnEssentialPropertiesChanged(); return removedCount; } ///  /// Removes a range of elements from the >. ///  /// The zero-based starting index of the range of elements to remove. /// The number of elements to remove. /// The specified range is exceeding the collection. public void RemoveRange(int index, int count) { if (index < 0) throw new ArgumentOutOfRangeException(nameof(index)); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (index + count > Count) throw new ArgumentOutOfRangeException(nameof(index)); if (count == 0) return; if (count == 1) { RemoveItem(index); return; } //Items will always be List, see constructors var items = (List)Items; List removedItems = items.GetRange(index, count); CheckReentrancy(); items.RemoveRange(index, count); OnEssentialPropertiesChanged(); if (Count == 0) OnCollectionReset(); else OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, index)); } ///  /// Clears the current collection and replaces it with the specified collection, /// using the default . ///  /// The items to fill the collection with, after clearing it. ///  is null. public void ReplaceRange(IEnumerable collection) { ReplaceRange(0, Count, collection, EqualityComparer.Default); } ///  /// Clears the current collection and replaces it with the specified collection, /// using the specified comparer to skip equal items. ///  /// The items to fill the collection with, after clearing it. /// An  to be used /// to check whether an item in the same location already existed before, /// which in case it would not be added to the collection, and no event will be raised for it. ///  is null. ///  is null. public void ReplaceRange(IEnumerable collection, IEqualityComparer comparer) { ReplaceRange(0, Count, collection, comparer); } ///  /// Removes the specified range and inserts the specified collection, /// ignoring equal items (using ). ///  /// The index of where to start the replacement. /// The number of items to be replaced. /// The collection to insert in that location. ///  is out of range. ///  is out of range. ///  is null. public void ReplaceRange(int index, int count, IEnumerable collection) { ReplaceRange(index, count, collection, EqualityComparer.Default); } ///  /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact. ///  /// The index of where to start the replacement. /// The number of items to be replaced. /// The collection to insert in that location. /// The comparer to use when checking for equal items. ///  is out of range. ///  is out of range. ///  is null. ///  is null. public void ReplaceRange(int index, int count, IEnumerable collection, IEqualityComparer comparer) { if (index < 0) throw new ArgumentOutOfRangeException(nameof(index)); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (index + count > Count) throw new ArgumentOutOfRangeException(nameof(index)); if (collection == null) throw new ArgumentNullException(nameof(collection)); if (comparer == null) throw new ArgumentNullException(nameof(comparer)); if (collection is ICollection countable) { if (countable.Count == 0) { RemoveRange(index, count); return; } } else if (!ContainsAny(collection)) { RemoveRange(index, count); return; } if (index + count == 0) { InsertRange(0, collection); return; } if (!(collection is IList list)) list = new List(collection); using (BlockReentrancy()) using (DeferEvents()) { var rangeCount = index + count; var addedCount = list.Count; var changesMade = false; List newCluster = null, oldCluster = null; int i = index; for (; i < rangeCount && i - index < addedCount; i++) { //parallel position T old = this[i], @new = list[i - index]; if (comparer.Equals(old, @new)) { OnRangeReplaced(i, newCluster, oldCluster); continue; } else { Items[i] = @new; if (newCluster == null) { Debug.Assert(oldCluster == null); newCluster = new List { @new }; oldCluster = new List { old }; } else { newCluster.Add(@new); oldCluster.Add(old); } changesMade = true; } } OnRangeReplaced(i, newCluster, oldCluster); //exceeding position if (count != addedCount) { var items = (List)Items; if (count > addedCount) { var removedCount = rangeCount - addedCount; T[] removed = new T[removedCount]; items.CopyTo(i, removed, 0, removed.Length); items.RemoveRange(i, removedCount); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed, i)); } else { var k = i - index; T[] added = new T[addedCount - k]; for (int j = k; j < addedCount; j++) { T @new = list[j]; added[j - k] = @new; } items.InsertRange(i, added); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, i)); } OnEssentialPropertiesChanged(); } else if (changesMade) { OnIndexerPropertyChanged(); } } } #endregion Public Methods //------------------------------------------------------ // // Protected Methods // //------------------------------------------------------ #region Protected Methods ///  /// Called by base class Collection<T> when the list is being cleared; /// raises a CollectionChanged event to any listeners. ///  protected override void ClearItems() { if (Count == 0) return; CheckReentrancy(); base.ClearItems(); OnEssentialPropertiesChanged(); OnCollectionReset(); } ///  /// Called by base class Collection<T> when an item is set in list; /// raises a CollectionChanged event to any listeners. ///  protected override void SetItem(int index, T item) { if (Equals(this[index], item)) return; CheckReentrancy(); T originalItem = this[index]; base.SetItem(index, item); OnIndexerPropertyChanged(); OnCollectionChanged(NotifyCollectionChangedAction.Replace, originalItem, item, index); } ///  /// Raise CollectionChanged event to any listeners. /// Properties/methods modifying this ObservableCollection will raise /// a collection changed event through this virtual method. ///  ///  /// When overriding this method, either call its base implementation /// or call  to guard against reentrant collection changes. ///  protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (_deferredEvents != null) { _deferredEvents.Add(e); return; } base.OnCollectionChanged(e); } protected virtual IDisposable DeferEvents() => new DeferredEventsCollection(this); #endregion Protected Methods //------------------------------------------------------ // // Private Methods // //------------------------------------------------------ #region Private Methods ///  /// Helper function to determine if a collection contains any elements. ///  /// The collection to evaluate. ///  private static bool ContainsAny(IEnumerable collection) { using (IEnumerator enumerator = collection.GetEnumerator()) return enumerator.MoveNext(); } ///  /// Helper to raise Count property and the Indexer property. ///  private void OnEssentialPropertiesChanged() { OnPropertyChanged(EventArgsCache.CountPropertyChanged); OnIndexerPropertyChanged(); } ///  /// /// Helper to raise a PropertyChanged event for the Indexer property /// ///  private void OnIndexerPropertyChanged() => OnPropertyChanged(EventArgsCache.IndexerPropertyChanged); ///  /// Helper to raise CollectionChanged event to any listeners ///  private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index)); ///  /// Helper to raise CollectionChanged event with action == Reset to any listeners ///  private void OnCollectionReset() => OnCollectionChanged(EventArgsCache.ResetCollectionChanged); ///  /// Helper to raise event for clustered action and clear cluster. ///  /// The index of the item following the replacement block. ///  ///  //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerable collection, IEqualityComparer comparer), //move when supported language version updated. private void OnRangeReplaced(int followingItemIndex, ICollection newCluster, ICollection oldCluster) { if (oldCluster == null || oldCluster.Count == 0) { Debug.Assert(newCluster == null || newCluster.Count == 0); return; } OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Replace, new List(newCluster), new List(oldCluster), followingItemIndex - oldCluster.Count)); oldCluster.Clear(); newCluster.Clear(); } #endregion Private Methods //------------------------------------------------------ // // Private Types // //------------------------------------------------------ #region Private Types private sealed class DeferredEventsCollection : List, IDisposable { private readonly ObservableRangeCollection _collection; public DeferredEventsCollection(ObservableRangeCollection collection) { Debug.Assert(collection != null); Debug.Assert(collection._deferredEvents == null); _collection = collection; _collection._deferredEvents = this; } public void Dispose() { _collection._deferredEvents = null; foreach (var args in this) _collection.OnCollectionChanged(args); } } #endregion Private Types } ///  /// To be kept outside , since otherwise, a new instance will be created for each generic type used. ///  internal static class EventArgsCache { internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs("Count"); internal static readonly PropertyChangedEventArgs IndexerPropertyChanged = new PropertyChangedEventArgs("Item[]"); internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); } } 

You will have to be careful binding the UI to your custom collection — the Default CollectionView class only supports single notification of items.

The C# summarized descendant.

More reading: http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx

 public sealed class ObservableCollectionEx : ObservableCollection { #region Ctor public ObservableCollectionEx() { } public ObservableCollectionEx(List list) : base(list) { } public ObservableCollectionEx(IEnumerable collection) : base(collection) { } #endregion ///  /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). ///  public void AddRange( IEnumerable itemsToAdd, ECollectionChangeNotificationMode notificationMode = ECollectionChangeNotificationMode.Add) { if (itemsToAdd == null) { throw new ArgumentNullException("itemsToAdd"); } CheckReentrancy(); if (notificationMode == ECollectionChangeNotificationMode.Reset) { foreach (var i in itemsToAdd) { Items.Add(i); } OnPropertyChanged(new PropertyChangedEventArgs("Count")); OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); return; } int startIndex = Count; var changedItems = itemsToAdd is List ? (List) itemsToAdd : new List(itemsToAdd); foreach (var i in changedItems) { Items.Add(i); } OnPropertyChanged(new PropertyChangedEventArgs("Count")); OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, changedItems, startIndex)); } public enum ECollectionChangeNotificationMode { ///  /// Notifies that only a portion of data was changed and supplies the changed items (not supported by some elements, /// like CollectionView class). ///  Add, ///  /// Notifies that the entire collection was changed, does not supply the changed items (may be inneficient with large /// collections as requires the full update even if a small portion of items was added). ///  Reset } } 

Yes, adding your own Custom Observable Collection would be fair enough. Don’t forget to raise appropriate events regardless whether it is used by UI for the moment or not 😉 You will have to raise property change notification for “Item[]” property (required by WPF side and bound controls) as well as NotifyCollectionChangedEventArgs with a set of items added (your range). I’ve did such things (as well as sorting support and some other stuff) and had no problems with both Presentation and Code Behind layers.

As there might be a number of operations to do on an ObservableCollection for example Clear first then AddRange and then insert “All” item for a ComboBox I ended up with folowing solution:

 public static class LinqExtensions { public static ICollection AddRange(this ICollection source, IEnumerable addSource) { foreach(T item in addSource) { source.Add(item); } return source; } } public class ExtendedObservableCollection: ObservableCollection { public void Execute(Action> itemsAction) { itemsAction(Items); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } 

And example how to use it:

 MyDogs.Execute(items => { items.Clear(); items.AddRange(Context.Dogs); items.Insert(0, new Dog { Id = 0, Name = "All Dogs" }); }); 

The Reset notification will be called only once after Execute is finished processing the underlying list.

Here is some additional help for collection changed and UI issues:

ObservableRangeCollection should pass a test like

 [Test] public void TestAddRangeWhileBoundToListCollectionView() { int collectionChangedEventsCounter = 0; int propertyChangedEventsCounter = 0; var collection = new ObservableRangeCollection(); collection.CollectionChanged += (sender, e) => { collectionChangedEventsCounter++; }; (collection as INotifyPropertyChanged).PropertyChanged += (sender, e) => { propertyChangedEventsCounter++; }; var list = new ListCollectionView(collection); collection.AddRange(new[] { new object(), new object(), new object(), new object() }); Assert.AreEqual(4, collection.Count); Assert.AreEqual(1, collectionChangedEventsCounter); Assert.AreEqual(2, propertyChangedEventsCounter); } 

otherwise we get

 System.NotSupportedException : Range actions are not supported. 

while using with a control.

I do not see an ideal solution, but NotifyCollectionChangedAction.Reset instead of Add/Remove partially solve the problem. See http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx as was mentioned by net_prog

Here’s a modification of the accepted answer to provide more functionality.

RangeCollection.cs:

 public class RangeCollection : ObservableCollection { #region Members ///  /// Occurs when a single item is added. ///  public event EventHandler> ItemAdded; ///  /// Occurs when a single item is inserted. ///  public event EventHandler> ItemInserted; ///  /// Occurs when a single item is removed. ///  public event EventHandler> ItemRemoved; ///  /// Occurs when a single item is replaced. ///  public event EventHandler> ItemReplaced; ///  /// Occurs when items are added to this. ///  public event EventHandler> ItemsAdded; ///  /// Occurs when items are removed from this. ///  public event EventHandler> ItemsRemoved; ///  /// Occurs when items are replaced within this. ///  public event EventHandler> ItemsReplaced; ///  /// Occurs when entire collection is cleared. ///  public event EventHandler> ItemsCleared; ///  /// Occurs when entire collection is replaced. ///  public event EventHandler> CollectionReplaced; #endregion #region Helper Methods ///  /// Throws exception if any of the specified objects are null. ///  private void Check(params T[] Items) { foreach (T Item in Items) { if (Item == null) { throw new ArgumentNullException("Item cannot be null."); } } } private void Check(IEnumerable Items) { if (Items == null) throw new ArgumentNullException("Items cannot be null."); } private void Check(IEnumerable> Items) { if (Items == null) throw new ArgumentNullException("Items cannot be null."); } private void RaiseChanged(NotifyCollectionChangedAction Action) { this.OnPropertyChanged(new PropertyChangedEventArgs("Count")); this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } #endregion #region Bulk Methods ///  /// Adds the elements of the specified collection to the end of this. ///  public void AddRange(IEnumerable NewItems) { this.Check(NewItems); foreach (var i in NewItems) this.Items.Add(i); this.RaiseChanged(NotifyCollectionChangedAction.Reset); this.OnItemsAdded(new ItemsAddedEventArgs(NewItems)); } ///  /// Adds variable IEnumerable to this. ///  ///  public void AddRange(params IEnumerable[] NewItems) { this.Check(NewItems); foreach (IEnumerable Items in NewItems) foreach (T Item in Items) this.Items.Add(Item); this.RaiseChanged(NotifyCollectionChangedAction.Reset); //TO-DO: Raise OnItemsAdded with combined IEnumerable. } ///  /// Removes the first occurence of each item in the specified collection. ///  public void Remove(IEnumerable OldItems) { this.Check(OldItems); foreach (var i in OldItems) Items.Remove(i); this.RaiseChanged(NotifyCollectionChangedAction.Reset); OnItemsRemoved(new ItemsRemovedEventArgs(OldItems)); } ///  /// Removes all occurences of each item in the specified collection. ///  ///  public void RemoveAll(IEnumerable OldItems) { this.Check(OldItems); var set = new HashSet(OldItems); var list = this as List; int i = 0; while (i < this.Count) if (set.Contains(this[i])) this.RemoveAt(i); else i++; this.RaiseChanged(NotifyCollectionChangedAction.Reset); OnItemsRemoved(new ItemsRemovedEventArgs(OldItems)); } ///  /// Replaces all occurences of a single item with specified item. ///  public void ReplaceAll(T Old, T New) { this.Check(Old, New); this.Replace(Old, New, false); this.RaiseChanged(NotifyCollectionChangedAction.Reset); this.OnItemReplaced(new ItemReplacedEventArgs(Old, New)); } ///  /// Clears this and adds specified collection. ///  public void ReplaceCollection(IEnumerable NewItems, bool SupressEvent = false) { this.Check(NewItems); IEnumerable OldItems = new List(this.Items); this.Items.Clear(); foreach (T Item in NewItems) this.Items.Add(Item); this.RaiseChanged(NotifyCollectionChangedAction.Reset); this.OnReplaced(new CollectionReplacedEventArgs(OldItems, NewItems)); } private void Replace(T Old, T New, bool BreakFirst) { List Cloned = new List(this.Items); int i = 0; foreach (T Item in Cloned) { if (Item.Equals(Old)) { this.Items.Remove(Item); this.Items.Insert(i, New); if (BreakFirst) break; } i++; } } ///  /// Replaces the first occurence of a single item with specified item. ///  public void Replace(T Old, T New) { this.Check(Old, New); this.Replace(Old, New, true); this.RaiseChanged(NotifyCollectionChangedAction.Reset); this.OnItemReplaced(new ItemReplacedEventArgs(Old, New)); } #endregion #region New Methods ///  /// Removes a single item. ///  ///  public new void Remove(T Item) { this.Check(Item); base.Remove(Item); OnItemRemoved(new ItemRemovedEventArgs(Item)); } ///  /// Removes a single item at specified index. ///  ///  public new void RemoveAt(int i) { T OldItem = this.Items[i]; //This will throw first if null base.RemoveAt(i); OnItemRemoved(new ItemRemovedEventArgs(OldItem)); } ///  /// Clears this. ///  public new void Clear() { IEnumerable OldItems = new List(this.Items); this.Items.Clear(); this.RaiseChanged(NotifyCollectionChangedAction.Reset); this.OnCleared(new ItemsClearedEventArgs(OldItems)); } ///  /// Adds a single item to end of this. ///  ///  public new void Add(T Item) { this.Check(Item); base.Add(Item); this.OnItemAdded(new ItemAddedEventArgs(Item)); } ///  /// Inserts a single item at specified index. ///  ///  ///  public new void Insert(int i, T Item) { this.Check(Item); base.Insert(i, Item); this.OnItemInserted(new ItemInsertedEventArgs(Item, i)); } ///  /// Returns list of T.ToString(). ///  ///  public new IEnumerable ToString() { foreach (T Item in this) yield return Item.ToString(); } #endregion #region Event Methods private void OnItemAdded(ItemAddedEventArgs i) { if (this.ItemAdded != null) this.ItemAdded(this, new ItemAddedEventArgs(i.NewItem)); } private void OnItemInserted(ItemInsertedEventArgs i) { if (this.ItemInserted != null) this.ItemInserted(this, new ItemInsertedEventArgs(i.NewItem, i.Index)); } private void OnItemRemoved(ItemRemovedEventArgs i) { if (this.ItemRemoved != null) this.ItemRemoved(this, new ItemRemovedEventArgs(i.OldItem)); } private void OnItemReplaced(ItemReplacedEventArgs i) { if (this.ItemReplaced != null) this.ItemReplaced(this, new ItemReplacedEventArgs(i.OldItem, i.NewItem)); } private void OnItemsAdded(ItemsAddedEventArgs i) { if (this.ItemsAdded != null) this.ItemsAdded(this, new ItemsAddedEventArgs(i.NewItems)); } private void OnItemsRemoved(ItemsRemovedEventArgs i) { if (this.ItemsRemoved != null) this.ItemsRemoved(this, new ItemsRemovedEventArgs(i.OldItems)); } private void OnItemsReplaced(ItemsReplacedEventArgs i) { if (this.ItemsReplaced != null) this.ItemsReplaced(this, new ItemsReplacedEventArgs(i.OldItems, i.NewItems)); } private void OnCleared(ItemsClearedEventArgs i) { if (this.ItemsCleared != null) this.ItemsCleared(this, new ItemsClearedEventArgs(i.OldItems)); } private void OnReplaced(CollectionReplacedEventArgs i) { if (this.CollectionReplaced != null) this.CollectionReplaced(this, new CollectionReplacedEventArgs(i.OldItems, i.NewItems)); } #endregion #region RangeCollection ///  /// Initializes a new instance. ///  public RangeCollection() : base() { } ///  /// Initializes a new instance from specified enumerable. ///  public RangeCollection(IEnumerable Collection) : base(Collection) { } ///  /// Initializes a new instance from specified list. ///  public RangeCollection(List List) : base(List) { } ///  /// Initializes a new instance with variable T. ///  public RangeCollection(params T[] Items) : base() { this.AddRange(Items); } ///  /// Initializes a new instance with variable enumerable. ///  public RangeCollection(params IEnumerable[] Items) : base() { this.AddRange(Items); } #endregion } 

Events Classes:

 public class CollectionReplacedEventArgs : ReplacedEventArgs { public CollectionReplacedEventArgs(IEnumerable Old, IEnumerable New) : base(Old, New) { } } public class ItemAddedEventArgs : EventArgs { public T NewItem; public ItemAddedEventArgs(T t) { this.NewItem = t; } } public class ItemInsertedEventArgs : EventArgs { public int Index; public T NewItem; public ItemInsertedEventArgs(T t, int i) { this.NewItem = t; this.Index = i; } } public class ItemRemovedEventArgs : EventArgs { public T OldItem; public ItemRemovedEventArgs(T t) { this.OldItem = t; } } public class ItemReplacedEventArgs : EventArgs { public T OldItem; public T NewItem; public ItemReplacedEventArgs(T Old, T New) { this.OldItem = Old; this.NewItem = New; } } public class ItemsAddedEventArgs : EventArgs { public IEnumerable NewItems; public ItemsAddedEventArgs(IEnumerable t) { this.NewItems = t; } } public class ItemsClearedEventArgs : RemovedEventArgs { public ItemsClearedEventArgs(IEnumerable Old) : base(Old) { } } public class ItemsRemovedEventArgs : RemovedEventArgs { public ItemsRemovedEventArgs(IEnumerable Old) : base(Old) { } } public class ItemsReplacedEventArgs : ReplacedEventArgs { public ItemsReplacedEventArgs(IEnumerable Old, IEnumerable New) : base(Old, New) { } } public class RemovedEventArgs : EventArgs { public IEnumerable OldItems; public RemovedEventArgs(IEnumerable Old) { this.OldItems = Old; } } public class ReplacedEventArgs : EventArgs { public IEnumerable OldItems; public IEnumerable NewItems; public ReplacedEventArgs(IEnumerable Old, IEnumerable New) { this.OldItems = Old; this.NewItems = New; } } 

Note: I did not manually raise OnCollectionChanged in the base methods because it appears only to be possible to create a CollectionChangedEventArgs using the Reset action. If you try to raise OnCollectionChanged using Reset for a single item change, your items control will appear to flicker, which is something you want to avoid.

You can also use this code to extend ObservableCollection:

 public static class ObservableCollectionExtend { public static void AddRange(this ObservableCollection source, IEnumerable items) { foreach (var item in items) { source.Add(item); } } } 

Then you don’t need to change class in existing code.