PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/Utilities/Collections/ChangeableList.cs

#
C# | 662 lines | 372 code | 54 blank | 236 comment | 27 complexity | 6a157db65e4d27f88990dd1f7976acb9 MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using Delta.Utilities.Helpers;
  5. using NUnit.Framework;
  6. namespace Delta.Utilities.Collections
  7. {
  8. /// <summary>
  9. /// Changeable list is basically just a List, which allows
  10. /// you to add and remove elements while enumerating.
  11. /// When ALL enumerations are complete, all the remembered removed
  12. /// and added elements will be applied (we can't do that while enumerating).
  13. /// </summary>
  14. /// <remarks>
  15. /// You can use this list like a normal List and will never notice
  16. /// any difference except when adding or removing entries while enumerating,
  17. /// which will thrown an exception for normal Lists.
  18. /// When the enumeration is complete and there are no other pending
  19. /// enumerations, the ApplyAddedAndRemovedElements() function is called
  20. /// and all added and removed elements are added and removed.
  21. /// Another way would be to copy the whole list for the enumeration,
  22. /// but this is much slower and usually we don't change the list that
  23. /// often when enumerating (see slower approach here:)
  24. /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp01212002.asp
  25. /// </remarks>
  26. /// <typeparam name="T">T</typeparam>
  27. public class ChangeableList<T>
  28. : IEnumerable<T>, ICollection<T>,
  29. ICloneable<ChangeableList<T>>
  30. {
  31. #region ChangeableEnumerator Class
  32. /// <summary>
  33. /// Changeable enumerator
  34. /// </summary>
  35. public class ChangeableEnumerator : IEnumerator<T>, IDisposable
  36. {
  37. #region currentIndex (Public)
  38. /// <summary>
  39. /// Own index in list.
  40. /// </summary>
  41. public int currentIndex = -1;
  42. #endregion
  43. #region Current (Public)
  44. /// <summary>
  45. /// Current item for enumerating.
  46. /// </summary>
  47. public T Current
  48. {
  49. get
  50. {
  51. if (currentIndex < 0 ||
  52. currentIndex >= list.Count)
  53. {
  54. return default(T);
  55. }
  56. else
  57. {
  58. return list[currentIndex];
  59. }
  60. } // get
  61. }
  62. #endregion
  63. #region Private
  64. #region list (Private)
  65. /// <summary>
  66. /// The list we are working on.
  67. /// </summary>
  68. private readonly ChangeableList<T> list;
  69. #endregion
  70. #region enumerationStarted (Private)
  71. /// <summary>
  72. /// Started enumeration?
  73. /// </summary>
  74. private bool enumerationStarted;
  75. #endregion
  76. #region disposed (Private)
  77. /// <summary>
  78. /// Disposed
  79. /// </summary>
  80. /// <returns>False</returns>
  81. private bool disposed;
  82. #endregion
  83. #region Current (Private)
  84. /// <summary>
  85. /// IEnumerator. current
  86. /// </summary>
  87. /// <returns>Object</returns>
  88. object IEnumerator.Current
  89. {
  90. get
  91. {
  92. return Current;
  93. } // get
  94. }
  95. #endregion
  96. #endregion
  97. #region Constructors
  98. /// <summary>
  99. /// Create changeable enumerator
  100. /// </summary>
  101. public ChangeableEnumerator(ChangeableList<T> setList)
  102. {
  103. list = setList;
  104. if (enumerationStarted == false)
  105. {
  106. list.enumerationCount++;
  107. }
  108. enumerationStarted = true;
  109. }
  110. #endregion
  111. #region Destructor
  112. /// <summary>
  113. /// Finalizer.
  114. /// </summary>
  115. ~ChangeableEnumerator()
  116. {
  117. Dispose();
  118. }
  119. #endregion
  120. #region IDisposable Members
  121. /// <summary>
  122. /// Dispose
  123. /// </summary>
  124. public void Dispose()
  125. {
  126. if (disposed)
  127. {
  128. return;
  129. }
  130. disposed = true;
  131. if (enumerationStarted)
  132. {
  133. list.ReduceEnumerationCount();
  134. }
  135. enumerationStarted = false;
  136. GC.SuppressFinalize(this);
  137. }
  138. #endregion
  139. #region IEnumerator Members
  140. /// <summary>
  141. /// Move next for enumeration.
  142. /// </summary>
  143. /// <returns>True if there was a next entry</returns>
  144. public bool MoveNext()
  145. {
  146. currentIndex++;
  147. bool ret = currentIndex < list.Count;
  148. if (ret == false)
  149. {
  150. if (enumerationStarted)
  151. {
  152. list.ReduceEnumerationCount();
  153. }
  154. enumerationStarted = false;
  155. } // if (ret)
  156. return ret;
  157. }
  158. /// <summary>
  159. /// Reset enumeration.
  160. /// </summary>
  161. public void Reset()
  162. {
  163. currentIndex = -1;
  164. if (enumerationStarted == false)
  165. {
  166. list.enumerationCount++;
  167. }
  168. enumerationStarted = true;
  169. }
  170. #endregion
  171. }
  172. #endregion
  173. #region Item (Public)
  174. /// <summary>
  175. /// This
  176. /// </summary>
  177. /// <param name="index">Index</param>
  178. /// <returns>T</returns>
  179. public T this[int index]
  180. {
  181. get
  182. {
  183. return data[index];
  184. } // get
  185. set
  186. {
  187. data[index] = value;
  188. } // set
  189. }
  190. #endregion
  191. #region Count (Public)
  192. /// <summary>
  193. /// Count
  194. /// </summary>
  195. /// <typeparam name="T">T</typeparam>
  196. /// <returns>Int</returns>
  197. public int Count
  198. {
  199. get
  200. {
  201. return data.Count;
  202. } // get
  203. }
  204. #endregion
  205. #region IsReadOnly (Public)
  206. /// <summary>
  207. /// Is read only? Will always return false.
  208. /// </summary>
  209. public bool IsReadOnly
  210. {
  211. get
  212. {
  213. return false;
  214. } // get
  215. }
  216. #endregion
  217. #region Protected
  218. #region remElementsToBeAdded (Protected)
  219. /// <summary>
  220. /// Remembered elements to be added and removed when finished
  221. /// enumerating. If we are not enumerating, adding and removing it
  222. /// done directly (this lists are empty then).
  223. /// Usually all these lists are empty, it will not happen very
  224. /// often that we want to add or remove elements while enumerating.
  225. /// </summary>
  226. /// <typeparam name="T">T</typeparam>
  227. protected List<T>
  228. //Remember elements to be removed
  229. remElementsToBeAdded = new List<T>();
  230. #endregion
  231. #region remElementsToBeRemoved (Protected)
  232. /// <summary>
  233. /// Remembered elements to be added and removed when finished
  234. /// enumerating. If we are not enumerating, adding and removing it
  235. /// done directly (this lists are empty then).
  236. /// Usually all these lists are empty, it will not happen very
  237. /// often that we want to add or remove elements while enumerating.
  238. /// </summary>
  239. /// <typeparam name="T">T</typeparam>
  240. protected List<T>
  241. //Remember elements to be removed
  242. remElementsToBeRemoved = new List<T>();
  243. #endregion
  244. ///// <summary>
  245. ///// remElementsToBeInserted is not just a list of objects, it
  246. ///// will use the InsertObject helper struct!
  247. ///// </summary>
  248. //protected List<InsertObject>
  249. // remElementsToBeInserted = new List<InsertObject>();
  250. #region enumerationCount (Protected)
  251. /// <summary>
  252. /// This value will be greater than 0 if currently in any enumeration!
  253. /// If this is 0, the list is modified directly, else we have to wait
  254. /// until all enumerations are complete.
  255. /// </summary>
  256. /// <typeparam name="T">T</typeparam>
  257. protected int enumerationCount;
  258. #endregion
  259. #endregion
  260. #region Private
  261. #region data (Private)
  262. /// <summary>
  263. /// We have to use this internal list because we can't derive from
  264. /// generic lists, most methods will pass on the method call to this class.
  265. /// </summary>
  266. /// <typeparam name="T">Type for the data list</typeparam>
  267. private readonly List<T> data = new List<T>();
  268. #endregion
  269. #endregion
  270. #region Constructors
  271. /// <summary>
  272. /// Create changeable list
  273. /// </summary>
  274. public ChangeableList()
  275. {
  276. }
  277. /// <summary>
  278. /// Create changeable list
  279. /// </summary>
  280. /// <param name="copyFrom">Copy data from this enumerable collection,
  281. /// this collection should not have any pending enumerations open, else
  282. /// we might miss added/removed entries.
  283. /// </param>
  284. public ChangeableList(IEnumerable<T> copyFrom)
  285. {
  286. data.InsertRange(0, copyFrom);
  287. }
  288. #endregion
  289. #region ICloneable<ChangeableList<T>> Members
  290. /// <summary>
  291. /// Clone changeable list.
  292. /// </summary>
  293. /// <returns>Cloned ChangeableList</returns>
  294. public ChangeableList<T> Clone()
  295. {
  296. return new ChangeableList<T>(this);
  297. }
  298. #endregion
  299. #region ICollection<T> Members
  300. /// <summary>
  301. /// Add new element to list. If we are currently enumerating, we can't
  302. /// add new elements, so we will remember them when all enumerations
  303. /// are complete. If not enumerating just add the new element.
  304. /// </summary>
  305. /// <param name="item">Item</param>
  306. public virtual void Add(T item)
  307. {
  308. // If currently enumerating, remember this value to add later
  309. if (enumerationCount > 0)
  310. {
  311. remElementsToBeAdded.Add(item);
  312. }
  313. else
  314. {
  315. data.Add(item);
  316. }
  317. }
  318. /// <summary>
  319. /// Clear whole list, this can actually be called inside an enumeration,
  320. /// the enumeration will complete and after that the whole list will
  321. /// be cleared!
  322. /// </summary>
  323. public void Clear()
  324. {
  325. if (enumerationCount > 0)
  326. {
  327. remElementsToBeRemoved.AddRange(data);
  328. }
  329. else
  330. {
  331. data.Clear();
  332. }
  333. }
  334. /// <summary>
  335. /// Does this list contain the given item?
  336. /// </summary>
  337. /// <param name="item">Item to check for</param>
  338. /// <returns>True if the item was found, false otherwise</returns>
  339. public bool Contains(T item)
  340. {
  341. return data.Contains(item);
  342. }
  343. /// <summary>
  344. /// Copy all values to target array, which must be big enough.
  345. /// </summary>
  346. /// <param name="array">Array to copy into</param>
  347. /// <param name="arrayIndex">Index to start copying into</param>
  348. public void CopyTo(T[] array, int arrayIndex)
  349. {
  350. data.CopyTo(array, arrayIndex);
  351. }
  352. /// <summary>
  353. /// Remove element from list. If we are currently enumerating, we can't
  354. /// remove directly, so we will remember elements to remove when all
  355. /// enumerations are complete. If not enumerating just remove the element.
  356. /// </summary>
  357. /// <param name="item">Item</param>
  358. /// <returns>
  359. /// True if removing the item succeeded, false otherwise.
  360. /// </returns>
  361. public bool Remove(T item)
  362. {
  363. if (enumerationCount > 0)
  364. {
  365. // Check if object does exists in list
  366. int index = IndexOf(item);
  367. if (index >= 0)
  368. {
  369. remElementsToBeRemoved.Add(item);
  370. }
  371. return index >= 0;
  372. } // if (enumerationCount)
  373. return data.Remove(item);
  374. }
  375. #endregion
  376. #region IEnumerable Members
  377. /// <summary>
  378. /// IEnumerable. get enumerator
  379. /// </summary>
  380. /// <returns>IEnumerator</returns>
  381. IEnumerator IEnumerable.GetEnumerator()
  382. {
  383. return GetEnumerator();
  384. }
  385. #endregion
  386. #region IEnumerable<T> Members
  387. /// <summary>
  388. /// GetEnumerator
  389. /// </summary>
  390. /// <returns>IEnumerator</returns>
  391. public IEnumerator<T> GetEnumerator()
  392. {
  393. return new ChangeableEnumerator(this);
  394. }
  395. #endregion
  396. #region IndexOf (Public)
  397. /// <summary>
  398. /// Index of
  399. /// </summary>
  400. /// <param name="item">Item</param>
  401. /// <returns>Int</returns>
  402. public int IndexOf(T item)
  403. {
  404. return data.IndexOf(item);
  405. }
  406. #endregion
  407. #region RemoveAt (Public)
  408. /// <summary>
  409. /// Remove element from list. If we are currently enumerating, we can't
  410. /// remove directly, so we will remember elements to remove when all
  411. /// enumerations are complete. If not enumerating just remove the element.
  412. /// </summary>
  413. /// <param name="index">Index</param>
  414. public void RemoveAt(int index)
  415. {
  416. if (enumerationCount > 0)
  417. {
  418. // Get index of item we want to remove
  419. if (index >= 0 &&
  420. index < data.Count)
  421. {
  422. remElementsToBeRemoved.Add(data[index]);
  423. }
  424. } // if (enumerationCount)
  425. else
  426. {
  427. data.RemoveAt(index);
  428. }
  429. }
  430. #endregion
  431. #region AddRange (Public)
  432. /// <summary>
  433. /// Add range, will just use Add to add all elements.
  434. /// </summary>
  435. /// <param name="c">C</param>
  436. public virtual void AddRange(ICollection<T> c)
  437. {
  438. foreach (T obj in c)
  439. {
  440. Add(obj);
  441. }
  442. }
  443. #endregion
  444. #region ToArray (Public)
  445. /// <summary>
  446. /// Convert this changeable list to an array with the same data.
  447. /// </summary>
  448. /// <returns>Flat fixed array created from the list</returns>
  449. public T[] ToArray()
  450. {
  451. return data.ToArray();
  452. }
  453. #endregion
  454. #region Methods (Private)
  455. #region ReduceEnumerationCount
  456. /// <summary>
  457. /// Reduces the enumerationCount and when reaching 0 again
  458. /// all the remembered elements to be added and removed will
  459. /// be added and removed. This function is called from the enumerator.
  460. /// </summary>
  461. protected void ReduceEnumerationCount()
  462. {
  463. if (enumerationCount <= 0)
  464. {
  465. throw new InvalidOperationException(
  466. "Can't call ReduceEnumerationCount when enumerationCount is 0!");
  467. }
  468. enumerationCount--;
  469. // When reaching 0, execute all remembered elements
  470. if (enumerationCount == 0)
  471. {
  472. if (remElementsToBeAdded.Count == 0 &&
  473. //remElementsToBeInserted.Count == 0 &&
  474. remElementsToBeRemoved.Count == 0)
  475. {
  476. // In most cases nothing was added, just quit then
  477. return;
  478. }
  479. //foreach (InsertObject insertObj in remElementsToBeInserted)
  480. //{
  481. // data.Insert(insertObj.index, insertObj.obj);
  482. //}
  483. //remElementsToBeInserted.Clear();
  484. foreach (T obj in remElementsToBeAdded)
  485. {
  486. data.Add(obj);
  487. }
  488. remElementsToBeAdded.Clear();
  489. // Rem elements to be removed
  490. foreach (T obj in remElementsToBeRemoved)
  491. {
  492. data.Remove(obj);
  493. }
  494. remElementsToBeRemoved.Clear();
  495. } // if
  496. }
  497. #endregion
  498. #endregion
  499. }
  500. /// <summary>
  501. /// ChangeableList tests, must be declared outside of a generic class.
  502. /// </summary>
  503. internal class ChangeableListTests
  504. {
  505. #region TestChangeableList
  506. /// <summary>
  507. /// Test ChangeableList. Note: Too slow for a dynamic unit test.
  508. /// </summary>
  509. [Test]
  510. public void TestChangeableList()
  511. {
  512. // Create list
  513. ChangeableList<int> list = new ChangeableList<int>();
  514. list.Add(1);
  515. list.Add(3);
  516. list.Add(5);
  517. // Add element while enumerating (7) and remove all elements
  518. // with a value above 1 (except 5, which will add 7).
  519. // This will not work with other lists, only Changeable lists allow this!
  520. foreach (int num in list)
  521. {
  522. //Console.WriteLine("Num: " + num);
  523. if (num == 5)
  524. {
  525. list.Add(7);
  526. }
  527. else if (num > 1)
  528. {
  529. list.Remove(num);
  530. }
  531. } // foreach
  532. // The resulting list should now have changed from "1, 3, 5" to "1, 5, 7"
  533. Assert.Equal("1, 5, 7", list.Write());
  534. //Console.WriteLine("Before insert: "+ArrayHelper.Write(list));
  535. // Insert range of elements while enumerating
  536. //foreach (int num in list)
  537. //{
  538. //if (num == 5)
  539. // // Just add a couple of 2s at position 2
  540. // list.InsertRange(2, new int[] { 2, 2, 2 });
  541. // if (num == 7)
  542. // // Enumerate again, just for the fun of it
  543. // foreach (int num2 in list)
  544. // {
  545. // Assert.False(num2 == 2);
  546. // if (num2 == 1)
  547. // list.Insert(3, 10);
  548. // list.Remove(10);
  549. // } // if foreach
  550. //} // foreach
  551. // Ok, check if list contains "1, 5, 2, 10, 2, 2, 7" as expected
  552. //Assert.Equal("1, 5, 2, 10, 2, 2, 7",
  553. // ArrayHelper.Write(list));
  554. //Console.WriteLine("After insert: " + ArrayHelper.Write(list));
  555. // Do same stuff with a normal ArrayList and compare!
  556. List<int> cmpList = new List<int>();
  557. cmpList.AddRange(new[]
  558. {
  559. 1, 5
  560. });
  561. cmpList.InsertRange(2, new[]
  562. {
  563. 7, 7
  564. });
  565. cmpList.Remove(7);
  566. cmpList.Insert(3, 10);
  567. cmpList.RemoveAt(3);
  568. //Console.WriteLine("cmpList: " + ArrayHelper.Write(cmpList));
  569. Assert.True(ArrayHelper.Compare(
  570. list.ToArray(),
  571. cmpList.ToArray()));
  572. }
  573. #endregion
  574. #region TestCloningChangeableList
  575. /// <summary>
  576. /// Test cloning changeable list
  577. /// </summary>
  578. [Test]
  579. public void TestCloningChangeableList()
  580. {
  581. ChangeableList<int> testList = new ChangeableList<int>();
  582. testList.Add(1);
  583. testList.Add(2);
  584. testList.Add(3);
  585. foreach (int num1 in testList)
  586. {
  587. Assert.Equal(1, num1);
  588. // This will be added after the foreach loop!
  589. testList.Add(1);
  590. ChangeableList<int> testList2 = testList.Clone();
  591. foreach (int num2 in testList2)
  592. {
  593. Assert.Equal(1, num2);
  594. // This will be added after the internal foreach loop.
  595. testList2.Add(2);
  596. // The lists should be different
  597. Assert.False(testList == testList2);
  598. // But the data in it should be still equal.
  599. Assert.Equal(testList.Write(),
  600. testList2.Write());
  601. break;
  602. } // foreach (num2)
  603. break;
  604. } // foreach (num1)
  605. }
  606. #endregion
  607. }
  608. }