99namespace CodingNinja . Wpf . ObjectModel ;
1010
1111/// <summary>
12- /// Implementation of a dynamic data collection based on <see cref="Collection{T}"/>,
13- /// implementing <see cref="INotifyCollectionChanged"/> to notify listeners
14- /// when items get added, removed or the whole list is refreshed.
12+ /// <see cref="ObservableCollection{T}"/> that supports bulk operations to avoid frequent update notification events.
1513/// </summary>
14+ /// <typeparam name="T"></typeparam>
1615public class ObservableRangeCollection < T > : ObservableCollection < T >
1716{
1817 //------------------------------------------------------
@@ -37,26 +36,26 @@ public class ObservableRangeCollection<T> : ObservableCollection<T>
3736 #region Constructors
3837
3938 /// <summary>
40- /// Initializes a new instance of <see cref="ObservableCollection {T}"/> that is empty and has default initial capacity.
39+ /// Initializes a new instance of <see cref="ObservableRangeCollection {T}"/> that is empty and has default initial capacity.
4140 /// </summary>
4241 /// <param name="allowDuplicates">Whether duplicate items are allowed in the collection.</param>
43- /// <param name="comparer">Support for <see cref="AllowDuplicates"/>.</param>
42+ /// <param name="comparer">Supports for <see cref="AllowDuplicates"/>.</param>
4443 public ObservableRangeCollection ( bool allowDuplicates = true , EqualityComparer < T > ? comparer = null )
4544 {
4645 AllowDuplicates = allowDuplicates ;
4746 Comparer = comparer ?? EqualityComparer < T > . Default ;
4847 }
4948
5049 /// <summary>
51- /// Initializes a new instance of the <see cref="ObservableCollection {T}"/> class that contains
50+ /// Initializes a new instance of the <see cref="ObservableRangeCollection {T}"/> class that contains
5251 /// elements copied from the specified collection and has sufficient capacity
5352 /// to accommodate the number of elements copied.
5453 /// </summary>
5554 /// <param name="collection">The collection whose elements are copied to the new list.</param>
5655 /// <param name="allowDuplicates">Whether duplicate items are allowed in the collection.</param>
57- /// <param name="comparer">Support for <see cref="AllowDuplicates"/>.</param>
56+ /// <param name="comparer">Supports for <see cref="AllowDuplicates"/>.</param>
5857 /// <remarks>
59- /// The elements are copied onto the <see cref="ObservableCollection {T}"/> in the
58+ /// The elements are copied onto the <see cref="ObservableRangeCollection {T}"/> in the
6059 /// same order they are read by the enumerator of the collection.
6160 /// </remarks>
6261 /// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference.</exception>
@@ -67,14 +66,14 @@ public ObservableRangeCollection(IEnumerable<T> collection, bool allowDuplicates
6766 }
6867
6968 /// <summary>
70- /// Initializes a new instance of the <see cref="ObservableCollection {T}"/> class
69+ /// Initializes a new instance of the <see cref="ObservableRangeCollection {T}"/> class
7170 /// that contains elements copied from the specified list.
7271 /// </summary>
7372 /// <param name="list">The list whose elements are copied to the new list.</param>
7473 /// <param name="allowDuplicates">Whether duplicate items are allowed in the collection.</param>
75- /// <param name="comparer">Support for <see cref="AllowDuplicates"/>.</param>
74+ /// <param name="comparer">Supports for <see cref="AllowDuplicates"/>.</param>
7675 /// <remarks>
77- /// The elements are copied onto the <see cref="ObservableCollection {T}"/> in the
76+ /// The elements are copied onto the <see cref="ObservableRangeCollection {T}"/> in the
7877 /// same order they are read by the enumerator of the list.
7978 /// </remarks>
8079 /// <exception cref="ArgumentNullException"><paramref name="list"/> is a null reference.</exception>
@@ -103,7 +102,7 @@ public ObservableRangeCollection(List<T> list, bool allowDuplicates = true, Equa
103102 public bool AllowDuplicates { get ; set ; } = true ;
104103
105104 /// <summary>
106- /// Support for <see cref="AllowDuplicates"/>.
105+ /// Supports for <see cref="AllowDuplicates"/>.
107106 /// </summary>
108107 public EqualityComparer < T > Comparer { get ; }
109108
@@ -124,10 +123,11 @@ public ObservableRangeCollection(List<T> list, bool allowDuplicates = true, Equa
124123 /// The collection whose elements should be added to the end of the <see cref="ObservableCollection{T}"/>.
125124 /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
126125 /// </param>
126+ /// <returns>Returns the number of items successfully added.</returns>
127127 /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
128- public void AddRange ( IEnumerable < T > collection )
128+ public int AddRange ( IEnumerable < T > collection )
129129 {
130- InsertRange ( Count , collection ) ;
130+ return InsertRange ( Count , collection ) ;
131131 }
132132
133133 /// <summary>
@@ -138,9 +138,10 @@ public void AddRange(IEnumerable<T> collection)
138138 /// The collection whose elements should be inserted into the <see cref="List{T}"/>.
139139 /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
140140 /// </param>
141+ /// <returns>Returns the number of items successfully inserted.</returns>
141142 /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
142143 /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is not in the collection range.</exception>
143- public void InsertRange ( int index , IEnumerable < T > collection )
144+ public int InsertRange ( int index , IEnumerable < T > collection )
144145 {
145146 ArgumentNullException . ThrowIfNull ( nameof ( collection ) ) ;
146147
@@ -165,14 +166,14 @@ public void InsertRange(int index, IEnumerable<T> collection)
165166
166167 if ( limitedCount == 0 )
167168 {
168- return ;
169+ return 0 ;
169170 }
170171
171172 if ( limitedCount == 1 )
172173 {
173174 Add ( collection . First ( ) ) ;
174175
175- return ;
176+ return 1 ;
176177 }
177178
178179 CheckReentrancy ( ) ;
@@ -184,15 +185,18 @@ public void InsertRange(int index, IEnumerable<T> collection)
184185 OnEssentialPropertiesChanged ( ) ;
185186
186187 // changedItems cannot be IEnumerable(lazy type).
187- OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Add , collection . ToList ( ) , index ) ) ;
188+ var changedItems = collection . ToList ( ) ;
189+ OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Add , changedItems , index ) ) ;
190+
191+ return changedItems . Count ;
188192 }
189193
190194 /// <summary>
191195 /// Iterates over the collection and removes all items that satisfy the specified match.
192196 /// </summary>
193197 /// <remarks>The complexity is O(n).</remarks>
194- /// <param name="match">Match the item to be removed </param>
195- /// <returns>Returns the number of elements that where </returns>
198+ /// <param name="match">A function to test each element for a condition. </param>
199+ /// <returns>Returns the number of items successfully removed. </returns>
196200 /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
197201 public int RemoveAll ( Predicate < T > match )
198202 {
@@ -206,8 +210,8 @@ public int RemoveAll(Predicate<T> match)
206210 /// <remarks>The complexity is O(n).</remarks>
207211 /// <param name="index">The index of where to start performing the search.</param>
208212 /// <param name="count">The number of items to iterate on.</param>
209- /// <param name="match">Match the item to be removed .</param>
210- /// <returns>Returns the number of elements that where .</returns>
213+ /// <param name="match">A function to test each element for a condition .</param>
214+ /// <returns>Returns the number of items successfully removed .</returns>
211215 /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
212216 /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
213217 /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
@@ -225,7 +229,7 @@ public int RemoveAll(int index, int count, Predicate<T> match)
225229
226230 if ( index + count > Count )
227231 {
228- throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
232+ throw new ArgumentException ( $ " { nameof ( index ) } + { nameof ( count ) } must be less than or equal to the ObservableCollection.Count." ) ;
229233 }
230234
231235 ArgumentNullException . ThrowIfNull ( nameof ( match ) ) ;
@@ -292,42 +296,44 @@ public int RemoveAll(int index, int count, Predicate<T> match)
292296 /// <para>NOTE: Removed items starting index is not set because items are not guaranteed to be consecutive.</para>
293297 /// </summary>
294298 /// <param name="collection">The items to remove.</param>
299+ /// <returns>Returns the number of items successfully removed.</returns>
295300 /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
296- public void RemoveRange ( IEnumerable < T > collection )
301+ public int RemoveRange ( IEnumerable < T > collection )
297302 {
298303 ArgumentNullException . ThrowIfNull ( nameof ( collection ) ) ;
299304
300305 if ( Count == 0 )
301306 {
302- return ;
307+ return 0 ;
303308 }
304309
305310 int limitedCount = collection . Take ( 2 ) . Count ( ) ;
306311
307312 if ( limitedCount == 0 )
308313 {
309- return ;
314+ return 0 ;
310315 }
311316
312317 if ( limitedCount == 1 )
313318 {
314- Remove ( collection . First ( ) ) ;
319+ bool removed = Remove ( collection . First ( ) ) ;
315320
316- return ;
321+ return removed ? 1 : 0 ;
317322 }
318323
319324 CheckReentrancy ( ) ;
320325
321- bool raiseEvents = false ;
326+ int removedCount = 0 ;
322327
323328 foreach ( var item in collection )
324329 {
325- raiseEvents |= Items . Remove ( item ) ;
330+ bool removed = Items . Remove ( item ) ;
331+ removedCount += removed ? 1 : 0 ;
326332 }
327333
328- if ( ! raiseEvents )
334+ if ( removedCount == 0 )
329335 {
330- return ;
336+ return 0 ;
331337 }
332338
333339 OnEssentialPropertiesChanged ( ) ;
@@ -341,6 +347,8 @@ public void RemoveRange(IEnumerable<T> collection)
341347 // changedItems cannot be IEnumerable(lazy type).
342348 OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Remove , collection . ToList ( ) ) ) ;
343349 }
350+
351+ return removedCount ;
344352 }
345353
346354 /// <summary>
@@ -363,7 +371,7 @@ public void RemoveRange(int index, int count)
363371
364372 if ( index + count > Count )
365373 {
366- throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
374+ throw new ArgumentException ( $ " { nameof ( index ) } + { nameof ( count ) } must be less than or equal to the ObservableCollection.Count." ) ;
367375 }
368376
369377 if ( count == 0 )
@@ -378,6 +386,13 @@ public void RemoveRange(int index, int count)
378386 return ;
379387 }
380388
389+ if ( index == 0 && count == Count )
390+ {
391+ Clear ( ) ;
392+
393+ return ;
394+ }
395+
381396 // Items will always be List<T>, see constructors.
382397 var items = ( List < T > ) Items ;
383398 var removedItems = items . GetRange ( index , count ) ;
@@ -388,14 +403,7 @@ public void RemoveRange(int index, int count)
388403
389404 OnEssentialPropertiesChanged ( ) ;
390405
391- if ( Count == 0 )
392- {
393- OnCollectionReset ( ) ;
394- }
395- else
396- {
397- OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Remove , removedItems , index ) ) ;
398- }
406+ OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Remove , removedItems , index ) ) ;
399407 }
400408
401409 /// <summary>
@@ -419,7 +427,9 @@ public void ReplaceRange(IEnumerable<T> collection)
419427
420428 /// <summary>
421429 /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact.
430+ /// <para>When index and count are equal to 0, it is equivalent to InsertRange(0, collection).</para>
422431 /// </summary>
432+ /// <remarks>This method is roughly equivalent to <see cref="RemoveRange(Int32, Int32)"/> then <see cref="InsertRange(Int32, IEnumerable{T})"/>.</remarks>
423433 /// <param name="index">The index of where to start the replacement.</param>
424434 /// <param name="count">The number of items to be replaced.</param>
425435 /// <param name="collection">The collection to insert in that location.</param>
@@ -461,7 +471,7 @@ void OnRangeReplaced(int followingItemIndex, ICollection<T> newCluster, ICollect
461471
462472 if ( index + count > Count )
463473 {
464- throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
474+ throw new ArgumentException ( $ " { nameof ( index ) } + { nameof ( count ) } must be less than or equal to the ObservableCollection.Count." ) ;
465475 }
466476
467477 ArgumentNullException . ThrowIfNull ( nameof ( collection ) ) ;
@@ -597,10 +607,7 @@ protected override void ClearItems()
597607 return ;
598608 }
599609
600- CheckReentrancy ( ) ;
601610 base . ClearItems ( ) ;
602- OnEssentialPropertiesChanged ( ) ;
603- OnCollectionReset ( ) ;
604611 }
605612
606613 /// <summary>
@@ -659,12 +666,7 @@ protected override void SetItem(int index, T item)
659666 return ;
660667 }
661668
662- CheckReentrancy ( ) ;
663- var oldItem = this [ index ] ;
664669 base . SetItem ( index , item ) ;
665-
666- OnIndexerPropertyChanged ( ) ;
667- OnCollectionChanged ( NotifyCollectionChangedAction . Replace , oldItem ! , item ! , index ) ;
668670 }
669671
670672 #endregion Protected Methods
@@ -677,14 +679,6 @@ protected override void SetItem(int index, T item)
677679
678680 #region Private Methods
679681
680- /// <summary>
681- /// Helper to raise CollectionChanged event to any listeners.
682- /// </summary>
683- private void OnCollectionChanged ( NotifyCollectionChangedAction action , object oldItem , object newItem , int index )
684- {
685- OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( action , newItem , oldItem , index ) ) ;
686- }
687-
688682 /// <summary>
689683 /// Helper to raise CollectionChanged event with action == Reset to any listeners.
690684 /// </summary>
0 commit comments