CoreObservableCollection.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. using System;
  2. using System.Collections;
  3. using System.Collections.ObjectModel;
  4. using System.Threading;
  5. namespace InABox.Core
  6. {
  7. // Licensed to the .NET Foundation under one or more agreements.
  8. // The .NET Foundation licenses this file to you under the MIT license.
  9. // See the LICENSE file in the project root for more information.
  10. using System.Collections.Generic;
  11. using System.Collections.Specialized;
  12. using System.ComponentModel;
  13. using System.Diagnostics;
  14. using System.Linq;
  15. /// <summary>
  16. /// Implementation of a dynamic data collection based on generic Collection&lt;T&gt;,
  17. /// implementing INotifyCollectionChanged to notify listeners
  18. /// when items get added, removed or the whole list is refreshed.
  19. ///
  20. /// Modified to Implement Cross-thread Synchronization of events
  21. /// Will trigger event callbacks on creating, rather than current thread.
  22. /// Collections should be created on Msin UI thread the to prevent exceptions
  23. /// </summary>
  24. public class CoreObservableCollection<T> : ObservableCollection<T>
  25. {
  26. //------------------------------------------------------
  27. //
  28. // Private Fields
  29. //
  30. //------------------------------------------------------
  31. #region Private Fields
  32. [NonSerialized] private DeferredEventsCollection? _deferredEvents;
  33. [NonSerialized] private SynchronizationContext? _synchronizationContext = SynchronizationContext.Current;
  34. #endregion Private Fields
  35. //------------------------------------------------------
  36. //
  37. // Constructors
  38. //
  39. //------------------------------------------------------
  40. #region Constructors
  41. /// <summary>
  42. /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity.
  43. /// </summary>
  44. public CoreObservableCollection()
  45. {
  46. }
  47. /// <summary>
  48. /// Initializes a new instance of the ObservableCollection class that contains
  49. /// elements copied from the specified collection and has sufficient capacity
  50. /// to accommodate the number of elements copied.
  51. /// </summary>
  52. /// <param name="collection">The collection whose elements are copied to the new list.</param>
  53. /// <remarks>
  54. /// The elements are copied onto the ObservableCollection in the
  55. /// same order they are read by the enumerator of the collection.
  56. /// </remarks>
  57. /// <exception cref="ArgumentNullException"> collection is a null reference </exception>
  58. public CoreObservableCollection(IEnumerable<T> collection) : base(collection)
  59. {
  60. }
  61. /// <summary>
  62. /// Initializes a new instance of the ObservableCollection class
  63. /// that contains elements copied from the specified list
  64. /// </summary>
  65. /// <param name="list">The list whose elements are copied to the new list.</param>
  66. /// <remarks>
  67. /// The elements are copied onto the ObservableCollection in the
  68. /// same order they are read by the enumerator of the list.
  69. /// </remarks>
  70. /// <exception cref="ArgumentNullException"> list is a null reference </exception>
  71. public CoreObservableCollection(List<T> list) : base(list)
  72. {
  73. }
  74. #endregion Constructors
  75. //------------------------------------------------------
  76. //
  77. // Public Properties
  78. //
  79. //------------------------------------------------------
  80. #region Public Properties
  81. EqualityComparer<T>? _Comparer;
  82. public EqualityComparer<T> Comparer
  83. {
  84. get => _Comparer ??= EqualityComparer<T>.Default;
  85. private set => _Comparer = value;
  86. }
  87. /// <summary>
  88. /// Gets or sets a value indicating whether this collection acts as a <see cref="HashSet{T}"/>,
  89. /// disallowing duplicate items, based on <see cref="Comparer"/>.
  90. /// This might indeed consume background performance, but in the other hand,
  91. /// it will pay off in UI performance as less required UI updates are required.
  92. /// </summary>
  93. public bool AllowDuplicates { get; set; } = true;
  94. // If the collection is created on a non-UI thread, but needs to update the UI,
  95. // we should be able to set the UI context here to prevent exceptions
  96. public SynchronizationContext? SynchronizationContext
  97. {
  98. get => _synchronizationContext;
  99. set => _synchronizationContext = value;
  100. }
  101. #endregion Public Properties
  102. //------------------------------------------------------
  103. //
  104. // Public Methods
  105. //
  106. //------------------------------------------------------
  107. #region Public Methods
  108. /// <summary>
  109. /// Adds the elements of the specified collection to the end of the <see cref="ObservableCollection{T}"/>.
  110. /// </summary>
  111. /// <param name="collection">
  112. /// The collection whose elements should be added to the end of the <see cref="ObservableCollection{T}"/>.
  113. /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
  114. /// </param>
  115. /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
  116. public void AddRange(IEnumerable<T> collection)
  117. {
  118. InsertRange(Count, collection);
  119. }
  120. /// <summary>
  121. /// Inserts the elements of a collection into the <see cref="ObservableCollection{T}"/> at the specified index.
  122. /// </summary>
  123. /// <param name="index">The zero-based index at which the new elements should be inserted.</param>
  124. /// <param name="collection">The collection whose elements should be inserted into the List<T>.
  125. /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.</param>
  126. /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
  127. /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is not in the collection range.</exception>
  128. public void InsertRange(int index, IEnumerable<T> collection)
  129. {
  130. if (collection == null)
  131. throw new ArgumentNullException(nameof(collection));
  132. if (index < 0)
  133. throw new ArgumentOutOfRangeException(nameof(index));
  134. if (index > Count)
  135. throw new ArgumentOutOfRangeException(nameof(index));
  136. if (!AllowDuplicates)
  137. collection =
  138. collection
  139. .Distinct(Comparer)
  140. .Where(item => !Items.Contains(item, Comparer))
  141. .ToList();
  142. if (collection is ICollection<T> countable)
  143. {
  144. if (countable.Count == 0)
  145. return;
  146. }
  147. else if (!collection.Any())
  148. return;
  149. CheckReentrancy();
  150. //expand the following couple of lines when adding more constructors.
  151. var target = (List<T>)Items;
  152. target.InsertRange(index, collection);
  153. OnEssentialPropertiesChanged();
  154. if (!(collection is IList list))
  155. list = new List<T>(collection);
  156. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, index));
  157. }
  158. /// <summary>
  159. /// Removes the first occurence of each item in the specified collection from the <see cref="ObservableCollection{T}"/>.
  160. /// </summary>
  161. /// <param name="collection">The items to remove.</param>
  162. /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
  163. public void RemoveRange(IEnumerable<T> collection)
  164. {
  165. if (collection == null)
  166. throw new ArgumentNullException(nameof(collection));
  167. if (Count == 0)
  168. return;
  169. else if (collection is ICollection<T> countable)
  170. {
  171. if (countable.Count == 0)
  172. return;
  173. else if (countable.Count == 1)
  174. using (IEnumerator<T> enumerator = countable.GetEnumerator())
  175. {
  176. enumerator.MoveNext();
  177. Remove(enumerator.Current);
  178. return;
  179. }
  180. }
  181. else if (!collection.Any())
  182. return;
  183. CheckReentrancy();
  184. var clusters = new Dictionary<int, List<T>>();
  185. var lastIndex = -1;
  186. List<T>? lastCluster = null;
  187. foreach (T item in collection)
  188. {
  189. var index = IndexOf(item);
  190. if (index < 0)
  191. continue;
  192. Items.RemoveAt(index);
  193. if (lastIndex == index && lastCluster != null)
  194. lastCluster.Add(item);
  195. else
  196. clusters[lastIndex = index] = lastCluster = new List<T> { item };
  197. }
  198. OnEssentialPropertiesChanged();
  199. if (Count == 0)
  200. OnCollectionReset();
  201. else
  202. foreach (KeyValuePair<int, List<T>> cluster in clusters)
  203. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
  204. cluster.Value, cluster.Key));
  205. }
  206. /// <summary>
  207. /// Iterates over the collection and removes all items that satisfy the specified match.
  208. /// </summary>
  209. /// <remarks>The complexity is O(n).</remarks>
  210. /// <param name="match"></param>
  211. /// <returns>Returns the number of elements that where </returns>
  212. /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
  213. public int RemoveAll(Predicate<T> match)
  214. {
  215. return RemoveAll(0, Count, match);
  216. }
  217. /// <summary>
  218. /// Iterates over the specified range within the collection and removes all items that satisfy the specified match.
  219. /// </summary>
  220. /// <remarks>The complexity is O(n).</remarks>
  221. /// <param name="index">The index of where to start performing the search.</param>
  222. /// <param name="count">The number of items to iterate on.</param>
  223. /// <param name="match"></param>
  224. /// <returns>Returns the number of elements that where </returns>
  225. /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
  226. /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
  227. /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
  228. public int RemoveAll(int index, int count, Predicate<T> match)
  229. {
  230. if (index < 0)
  231. throw new ArgumentOutOfRangeException(nameof(index));
  232. if (count < 0)
  233. throw new ArgumentOutOfRangeException(nameof(count));
  234. if (index + count > Count)
  235. throw new ArgumentOutOfRangeException(nameof(index));
  236. if (match == null)
  237. throw new ArgumentNullException(nameof(match));
  238. if (Count == 0)
  239. return 0;
  240. List<T>? cluster = null;
  241. var clusterIndex = -1;
  242. var removedCount = 0;
  243. using (BlockReentrancy())
  244. using (DeferEvents())
  245. {
  246. for (var i = 0; i < count; i++, index++)
  247. {
  248. T item = Items[index];
  249. if (match(item))
  250. {
  251. Items.RemoveAt(index);
  252. removedCount++;
  253. if (clusterIndex == index)
  254. {
  255. Debug.Assert(cluster != null);
  256. cluster!.Add(item);
  257. }
  258. else
  259. {
  260. cluster = new List<T> { item };
  261. clusterIndex = index;
  262. }
  263. index--;
  264. }
  265. else if (clusterIndex > -1)
  266. {
  267. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
  268. cluster, clusterIndex));
  269. clusterIndex = -1;
  270. cluster = null;
  271. }
  272. }
  273. if (clusterIndex > -1)
  274. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
  275. cluster, clusterIndex));
  276. }
  277. if (removedCount > 0)
  278. OnEssentialPropertiesChanged();
  279. return removedCount;
  280. }
  281. /// <summary>
  282. /// Removes a range of elements from the <see cref="ObservableCollection{T}"/>>.
  283. /// </summary>
  284. /// <param name="index">The zero-based starting index of the range of elements to remove.</param>
  285. /// <param name="count">The number of elements to remove.</param>
  286. /// <exception cref="ArgumentOutOfRangeException">The specified range is exceeding the collection.</exception>
  287. public void RemoveRange(int index, int count)
  288. {
  289. if (index < 0)
  290. throw new ArgumentOutOfRangeException(nameof(index));
  291. if (count < 0)
  292. throw new ArgumentOutOfRangeException(nameof(count));
  293. if (index + count > Count)
  294. throw new ArgumentOutOfRangeException(nameof(index));
  295. if (count == 0)
  296. return;
  297. if (count == 1)
  298. {
  299. RemoveItem(index);
  300. return;
  301. }
  302. //Items will always be List<T>, see constructors
  303. var items = (List<T>)Items;
  304. List<T> removedItems = items.GetRange(index, count);
  305. CheckReentrancy();
  306. items.RemoveRange(index, count);
  307. OnEssentialPropertiesChanged();
  308. if (Count == 0)
  309. OnCollectionReset();
  310. else
  311. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
  312. removedItems, index));
  313. }
  314. /// <summary>
  315. /// Clears the current collection and replaces it with the specified collection,
  316. /// using <see cref="Comparer"/>.
  317. /// </summary>
  318. /// <param name="collection">The items to fill the collection with, after clearing it.</param>
  319. /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
  320. public void ReplaceRange(IEnumerable<T> collection)
  321. {
  322. ReplaceRange(0, Count, collection);
  323. }
  324. /// <summary>
  325. /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact.
  326. /// </summary>
  327. /// <param name="index">The index of where to start the replacement.</param>
  328. /// <param name="count">The number of items to be replaced.</param>
  329. /// <param name="collection">The collection to insert in that location.</param>
  330. /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
  331. /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
  332. /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
  333. /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is null.</exception>
  334. public void ReplaceRange(int index, int count, IEnumerable<T> collection)
  335. {
  336. if (index < 0)
  337. throw new ArgumentOutOfRangeException(nameof(index));
  338. if (count < 0)
  339. throw new ArgumentOutOfRangeException(nameof(count));
  340. if (index + count > Count)
  341. throw new ArgumentOutOfRangeException(nameof(index));
  342. if (collection == null)
  343. throw new ArgumentNullException(nameof(collection));
  344. if (!AllowDuplicates)
  345. collection =
  346. collection
  347. .Distinct(Comparer)
  348. .ToList();
  349. if (collection is ICollection<T> countable)
  350. {
  351. if (countable.Count == 0)
  352. {
  353. RemoveRange(index, count);
  354. return;
  355. }
  356. }
  357. else if (!collection.Any())
  358. {
  359. RemoveRange(index, count);
  360. return;
  361. }
  362. if (index + count == 0)
  363. {
  364. InsertRange(0, collection);
  365. return;
  366. }
  367. if (!(collection is IList<T> list))
  368. list = new List<T>(collection);
  369. using (BlockReentrancy())
  370. using (DeferEvents())
  371. {
  372. var rangeCount = index + count;
  373. var addedCount = list.Count;
  374. var changesMade = false;
  375. List<T>?
  376. newCluster = null,
  377. oldCluster = null;
  378. int i = index;
  379. for (; i < rangeCount && i - index < addedCount; i++)
  380. {
  381. try
  382. {
  383. // This threw an exception 2025-07-11 for some reason
  384. // i = 0; this.Length=0
  385. //parallel position
  386. T old = this[i], @new = list[i - index];
  387. if (Comparer.Equals(old, @new))
  388. {
  389. OnRangeReplaced(i, newCluster!, oldCluster!);
  390. continue;
  391. }
  392. else
  393. {
  394. Items[i] = @new;
  395. if (newCluster == null)
  396. {
  397. Debug.Assert(oldCluster == null);
  398. newCluster = new List<T> { @new };
  399. oldCluster = new List<T> { old };
  400. }
  401. else
  402. {
  403. newCluster.Add(@new);
  404. oldCluster!.Add(old);
  405. }
  406. changesMade = true;
  407. }
  408. }
  409. catch (Exception e)
  410. {
  411. // Not sure what to do with this yet
  412. throw (e);
  413. }
  414. }
  415. OnRangeReplaced(i, newCluster!, oldCluster!);
  416. //exceeding position
  417. if (count != addedCount)
  418. {
  419. var items = (List<T>)Items;
  420. if (count > addedCount)
  421. {
  422. var removedCount = rangeCount - addedCount;
  423. T[] removed = new T[removedCount];
  424. items.CopyTo(i, removed, 0, removed.Length);
  425. items.RemoveRange(i, removedCount);
  426. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
  427. removed, i));
  428. }
  429. else
  430. {
  431. var k = i - index;
  432. T[] added = new T[addedCount - k];
  433. for (int j = k; j < addedCount; j++)
  434. {
  435. T @new = list[j];
  436. added[j - k] = @new;
  437. }
  438. items.InsertRange(i, added);
  439. OnCollectionChanged(
  440. new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, i));
  441. }
  442. OnEssentialPropertiesChanged();
  443. }
  444. else if (changesMade)
  445. {
  446. OnIndexerPropertyChanged();
  447. }
  448. }
  449. }
  450. #endregion Public Methods
  451. //------------------------------------------------------
  452. //
  453. // Protected Methods
  454. //
  455. //------------------------------------------------------
  456. #region Protected Methods
  457. /// <summary>
  458. /// Called by base class Collection&lt;T&gt; when the list is being cleared;
  459. /// raises a CollectionChanged event to any listeners.
  460. /// </summary>
  461. protected override void ClearItems()
  462. {
  463. if (Count == 0)
  464. return;
  465. CheckReentrancy();
  466. base.ClearItems();
  467. OnEssentialPropertiesChanged();
  468. OnCollectionReset();
  469. }
  470. /// <inheritdoc/>
  471. protected override void InsertItem(int index, T item)
  472. {
  473. if (!AllowDuplicates && Items.Contains(item))
  474. return;
  475. base.InsertItem(index, item);
  476. }
  477. /// <inheritdoc/>
  478. protected override void SetItem(int index, T item)
  479. {
  480. if (AllowDuplicates)
  481. {
  482. if (Comparer.Equals(this[index], item))
  483. return;
  484. }
  485. else if (Items.Contains(item, Comparer))
  486. return;
  487. CheckReentrancy();
  488. T oldItem = this[index];
  489. base.SetItem(index, item);
  490. OnIndexerPropertyChanged();
  491. OnCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem!, item!, index);
  492. }
  493. /// <summary>
  494. /// Raise CollectionChanged event to any listeners.
  495. /// Properties/methods modifying this ObservableCollection will raise
  496. /// a collection changed event through this virtual method.
  497. /// </summary>
  498. /// <remarks>
  499. /// When overriding this method, either call its base implementation
  500. /// or call <see cref="BlockReentrancy"/> to guard against reentrant collection changes.
  501. /// </remarks>
  502. protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
  503. {
  504. if (_synchronizationContext == null || SynchronizationContext.Current == _synchronizationContext)
  505. {
  506. // Execute the CollectionChanged event on the current thread
  507. RaiseCollectionChanged(e);
  508. }
  509. else
  510. {
  511. // Raises the CollectionChanged event on the creator thread
  512. _synchronizationContext.Post(RaiseCollectionChanged, e);
  513. }
  514. }
  515. private void RaiseCollectionChanged(object? param)
  516. {
  517. // We are in the creator thread, call the base implementation directly
  518. if (param is NotifyCollectionChangedEventArgs args)
  519. {
  520. if (_deferredEvents != null)
  521. {
  522. _deferredEvents.Add(args);
  523. return;
  524. }
  525. base.OnCollectionChanged(args);
  526. }
  527. }
  528. protected virtual IDisposable DeferEvents() => new DeferredEventsCollection(this);
  529. #endregion Protected Methods
  530. //------------------------------------------------------
  531. //
  532. // Private Methods
  533. //
  534. //------------------------------------------------------
  535. #region Private Methods
  536. /// <summary>
  537. /// Helper to raise Count property and the Indexer property.
  538. /// </summary>
  539. void OnEssentialPropertiesChanged()
  540. {
  541. OnPropertyChanged(EventArgsCache.CountPropertyChanged);
  542. OnIndexerPropertyChanged();
  543. }
  544. /// <summary>
  545. /// /// Helper to raise a PropertyChanged event for the Indexer property
  546. /// /// </summary>
  547. void OnIndexerPropertyChanged() =>
  548. OnPropertyChanged(EventArgsCache.IndexerPropertyChanged);
  549. protected override void OnPropertyChanged(PropertyChangedEventArgs e)
  550. {
  551. if (_synchronizationContext == null || SynchronizationContext.Current == _synchronizationContext)
  552. {
  553. // Execute the PropertyChanged event on the current thread
  554. RaisePropertyChanged(e);
  555. }
  556. else
  557. {
  558. // Raises the PropertyChanged event on the creator thread
  559. _synchronizationContext.Post(RaisePropertyChanged, e);
  560. }
  561. }
  562. private void RaisePropertyChanged(object? param)
  563. {
  564. // We are in the creator thread, call the base implementation directly
  565. if (param is PropertyChangedEventArgs args)
  566. base.OnPropertyChanged(args);
  567. }
  568. /// <summary>
  569. /// Helper to raise CollectionChanged event to any listeners
  570. /// </summary>
  571. void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) =>
  572. OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
  573. /// <summary>
  574. /// Helper to raise CollectionChanged event with action == Reset to any listeners
  575. /// </summary>
  576. void OnCollectionReset() =>
  577. OnCollectionChanged(EventArgsCache.ResetCollectionChanged);
  578. /// <summary>
  579. /// Helper to raise event for clustered action and clear cluster.
  580. /// </summary>
  581. /// <param name="followingItemIndex">The index of the item following the replacement block.</param>
  582. /// <param name="newCluster"></param>
  583. /// <param name="oldCluster"></param>
  584. //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerable<T> collection, IEqualityComparer<T> comparer),
  585. //move when supported language version updated.
  586. void OnRangeReplaced(int followingItemIndex, ICollection<T> newCluster, ICollection<T> oldCluster)
  587. {
  588. if (oldCluster == null || oldCluster.Count == 0)
  589. {
  590. Debug.Assert(newCluster == null || newCluster.Count == 0);
  591. return;
  592. }
  593. OnCollectionChanged(
  594. new NotifyCollectionChangedEventArgs(
  595. NotifyCollectionChangedAction.Replace,
  596. new List<T>(newCluster),
  597. new List<T>(oldCluster),
  598. followingItemIndex - oldCluster.Count));
  599. oldCluster.Clear();
  600. newCluster.Clear();
  601. }
  602. #endregion Private Methods
  603. //------------------------------------------------------
  604. //
  605. // Private Types
  606. //
  607. //------------------------------------------------------
  608. #region Private Types
  609. sealed class DeferredEventsCollection : List<NotifyCollectionChangedEventArgs>, IDisposable
  610. {
  611. readonly CoreObservableCollection<T> _collection;
  612. public DeferredEventsCollection(CoreObservableCollection<T> collection)
  613. {
  614. //Debug.Assert(collection != null);
  615. //Debug.Assert(collection._deferredEvents == null);
  616. _collection = collection;
  617. _collection._deferredEvents = this;
  618. }
  619. public void Dispose()
  620. {
  621. _collection._deferredEvents = null;
  622. foreach (var args in this)
  623. _collection.OnCollectionChanged(args);
  624. }
  625. }
  626. #endregion Private Types
  627. }
  628. /// <remarks>
  629. /// To be kept outside <see cref="ObservableCollection{T}"/>, since otherwise, a new instance will be created for each generic type used.
  630. /// </remarks>
  631. internal static class EventArgsCache
  632. {
  633. internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs("Count");
  634. internal static readonly PropertyChangedEventArgs IndexerPropertyChanged =
  635. new PropertyChangedEventArgs("Item[]");
  636. internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged =
  637. new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
  638. }
  639. }