CoreObservableCollection.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  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. //parallel position
  382. T old = this[i], @new = list[i - index];
  383. if (Comparer.Equals(old, @new))
  384. {
  385. OnRangeReplaced(i, newCluster!, oldCluster!);
  386. continue;
  387. }
  388. else
  389. {
  390. Items[i] = @new;
  391. if (newCluster == null)
  392. {
  393. Debug.Assert(oldCluster == null);
  394. newCluster = new List<T> { @new };
  395. oldCluster = new List<T> { old };
  396. }
  397. else
  398. {
  399. newCluster.Add(@new);
  400. oldCluster!.Add(old);
  401. }
  402. changesMade = true;
  403. }
  404. }
  405. OnRangeReplaced(i, newCluster!, oldCluster!);
  406. //exceeding position
  407. if (count != addedCount)
  408. {
  409. var items = (List<T>)Items;
  410. if (count > addedCount)
  411. {
  412. var removedCount = rangeCount - addedCount;
  413. T[] removed = new T[removedCount];
  414. items.CopyTo(i, removed, 0, removed.Length);
  415. items.RemoveRange(i, removedCount);
  416. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
  417. removed, i));
  418. }
  419. else
  420. {
  421. var k = i - index;
  422. T[] added = new T[addedCount - k];
  423. for (int j = k; j < addedCount; j++)
  424. {
  425. T @new = list[j];
  426. added[j - k] = @new;
  427. }
  428. items.InsertRange(i, added);
  429. OnCollectionChanged(
  430. new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, i));
  431. }
  432. OnEssentialPropertiesChanged();
  433. }
  434. else if (changesMade)
  435. {
  436. OnIndexerPropertyChanged();
  437. }
  438. }
  439. }
  440. #endregion Public Methods
  441. //------------------------------------------------------
  442. //
  443. // Protected Methods
  444. //
  445. //------------------------------------------------------
  446. #region Protected Methods
  447. /// <summary>
  448. /// Called by base class Collection&lt;T&gt; when the list is being cleared;
  449. /// raises a CollectionChanged event to any listeners.
  450. /// </summary>
  451. protected override void ClearItems()
  452. {
  453. if (Count == 0)
  454. return;
  455. CheckReentrancy();
  456. base.ClearItems();
  457. OnEssentialPropertiesChanged();
  458. OnCollectionReset();
  459. }
  460. /// <inheritdoc/>
  461. protected override void InsertItem(int index, T item)
  462. {
  463. if (!AllowDuplicates && Items.Contains(item))
  464. return;
  465. base.InsertItem(index, item);
  466. }
  467. /// <inheritdoc/>
  468. protected override void SetItem(int index, T item)
  469. {
  470. if (AllowDuplicates)
  471. {
  472. if (Comparer.Equals(this[index], item))
  473. return;
  474. }
  475. else if (Items.Contains(item, Comparer))
  476. return;
  477. CheckReentrancy();
  478. T oldItem = this[index];
  479. base.SetItem(index, item);
  480. OnIndexerPropertyChanged();
  481. OnCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem!, item!, index);
  482. }
  483. /// <summary>
  484. /// Raise CollectionChanged event to any listeners.
  485. /// Properties/methods modifying this ObservableCollection will raise
  486. /// a collection changed event through this virtual method.
  487. /// </summary>
  488. /// <remarks>
  489. /// When overriding this method, either call its base implementation
  490. /// or call <see cref="BlockReentrancy"/> to guard against reentrant collection changes.
  491. /// </remarks>
  492. protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
  493. {
  494. if (_synchronizationContext == null || SynchronizationContext.Current == _synchronizationContext)
  495. {
  496. // Execute the CollectionChanged event on the current thread
  497. RaiseCollectionChanged(e);
  498. }
  499. else
  500. {
  501. // Raises the CollectionChanged event on the creator thread
  502. _synchronizationContext.Post(RaiseCollectionChanged, e);
  503. }
  504. }
  505. private void RaiseCollectionChanged(object? param)
  506. {
  507. // We are in the creator thread, call the base implementation directly
  508. if (param is NotifyCollectionChangedEventArgs args)
  509. {
  510. if (_deferredEvents != null)
  511. {
  512. _deferredEvents.Add(args);
  513. return;
  514. }
  515. base.OnCollectionChanged(args);
  516. }
  517. }
  518. protected virtual IDisposable DeferEvents() => new DeferredEventsCollection(this);
  519. #endregion Protected Methods
  520. //------------------------------------------------------
  521. //
  522. // Private Methods
  523. //
  524. //------------------------------------------------------
  525. #region Private Methods
  526. /// <summary>
  527. /// Helper to raise Count property and the Indexer property.
  528. /// </summary>
  529. void OnEssentialPropertiesChanged()
  530. {
  531. OnPropertyChanged(EventArgsCache.CountPropertyChanged);
  532. OnIndexerPropertyChanged();
  533. }
  534. /// <summary>
  535. /// /// Helper to raise a PropertyChanged event for the Indexer property
  536. /// /// </summary>
  537. void OnIndexerPropertyChanged() =>
  538. OnPropertyChanged(EventArgsCache.IndexerPropertyChanged);
  539. protected override void OnPropertyChanged(PropertyChangedEventArgs e)
  540. {
  541. if (_synchronizationContext == null || SynchronizationContext.Current == _synchronizationContext)
  542. {
  543. // Execute the PropertyChanged event on the current thread
  544. RaisePropertyChanged(e);
  545. }
  546. else
  547. {
  548. // Raises the PropertyChanged event on the creator thread
  549. _synchronizationContext.Post(RaisePropertyChanged, e);
  550. }
  551. }
  552. private void RaisePropertyChanged(object? param)
  553. {
  554. // We are in the creator thread, call the base implementation directly
  555. if (param is PropertyChangedEventArgs args)
  556. base.OnPropertyChanged(args);
  557. }
  558. /// <summary>
  559. /// Helper to raise CollectionChanged event to any listeners
  560. /// </summary>
  561. void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) =>
  562. OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
  563. /// <summary>
  564. /// Helper to raise CollectionChanged event with action == Reset to any listeners
  565. /// </summary>
  566. void OnCollectionReset() =>
  567. OnCollectionChanged(EventArgsCache.ResetCollectionChanged);
  568. /// <summary>
  569. /// Helper to raise event for clustered action and clear cluster.
  570. /// </summary>
  571. /// <param name="followingItemIndex">The index of the item following the replacement block.</param>
  572. /// <param name="newCluster"></param>
  573. /// <param name="oldCluster"></param>
  574. //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerable<T> collection, IEqualityComparer<T> comparer),
  575. //move when supported language version updated.
  576. void OnRangeReplaced(int followingItemIndex, ICollection<T> newCluster, ICollection<T> oldCluster)
  577. {
  578. if (oldCluster == null || oldCluster.Count == 0)
  579. {
  580. Debug.Assert(newCluster == null || newCluster.Count == 0);
  581. return;
  582. }
  583. OnCollectionChanged(
  584. new NotifyCollectionChangedEventArgs(
  585. NotifyCollectionChangedAction.Replace,
  586. new List<T>(newCluster),
  587. new List<T>(oldCluster),
  588. followingItemIndex - oldCluster.Count));
  589. oldCluster.Clear();
  590. newCluster.Clear();
  591. }
  592. #endregion Private Methods
  593. //------------------------------------------------------
  594. //
  595. // Private Types
  596. //
  597. //------------------------------------------------------
  598. #region Private Types
  599. sealed class DeferredEventsCollection : List<NotifyCollectionChangedEventArgs>, IDisposable
  600. {
  601. readonly CoreObservableCollection<T> _collection;
  602. public DeferredEventsCollection(CoreObservableCollection<T> collection)
  603. {
  604. Debug.Assert(collection != null);
  605. Debug.Assert(collection._deferredEvents == null);
  606. _collection = collection;
  607. _collection._deferredEvents = this;
  608. }
  609. public void Dispose()
  610. {
  611. _collection._deferredEvents = null;
  612. foreach (var args in this)
  613. _collection.OnCollectionChanged(args);
  614. }
  615. }
  616. #endregion Private Types
  617. }
  618. /// <remarks>
  619. /// To be kept outside <see cref="ObservableCollection{T}"/>, since otherwise, a new instance will be created for each generic type used.
  620. /// </remarks>
  621. internal static class EventArgsCache
  622. {
  623. internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs("Count");
  624. internal static readonly PropertyChangedEventArgs IndexerPropertyChanged =
  625. new PropertyChangedEventArgs("Item[]");
  626. internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged =
  627. new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
  628. }
  629. }