CompositeDisposable.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. using System;
  2. using System.Collections.Generic;
  3. // using System.Linq; do not use LINQ
  4. using System.Text;
  5. namespace UniRx
  6. {
  7. // copy, modified from Rx Official
  8. public sealed class CompositeDisposable : ICollection<IDisposable>, IDisposable, ICancelable
  9. {
  10. private readonly object _gate = new object();
  11. private bool _disposed;
  12. private List<IDisposable> _disposables;
  13. private int _count;
  14. private const int SHRINK_THRESHOLD = 64;
  15. /// <summary>
  16. /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.CompositeDisposable"/> class with no disposables contained by it initially.
  17. /// </summary>
  18. public CompositeDisposable()
  19. {
  20. _disposables = new List<IDisposable>();
  21. }
  22. /// <summary>
  23. /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.CompositeDisposable"/> class with the specified number of disposables.
  24. /// </summary>
  25. /// <param name="capacity">The number of disposables that the new CompositeDisposable can initially store.</param>
  26. /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than zero.</exception>
  27. public CompositeDisposable(int capacity)
  28. {
  29. if (capacity < 0)
  30. throw new ArgumentOutOfRangeException("capacity");
  31. _disposables = new List<IDisposable>(capacity);
  32. }
  33. /// <summary>
  34. /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.CompositeDisposable"/> class from a group of disposables.
  35. /// </summary>
  36. /// <param name="disposables">Disposables that will be disposed together.</param>
  37. /// <exception cref="ArgumentNullException"><paramref name="disposables"/> is null.</exception>
  38. public CompositeDisposable(params IDisposable[] disposables)
  39. {
  40. if (disposables == null)
  41. throw new ArgumentNullException("disposables");
  42. _disposables = new List<IDisposable>(disposables);
  43. _count = _disposables.Count;
  44. }
  45. /// <summary>
  46. /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.CompositeDisposable"/> class from a group of disposables.
  47. /// </summary>
  48. /// <param name="disposables">Disposables that will be disposed together.</param>
  49. /// <exception cref="ArgumentNullException"><paramref name="disposables"/> is null.</exception>
  50. public CompositeDisposable(IEnumerable<IDisposable> disposables)
  51. {
  52. if (disposables == null)
  53. throw new ArgumentNullException("disposables");
  54. _disposables = new List<IDisposable>(disposables);
  55. _count = _disposables.Count;
  56. }
  57. /// <summary>
  58. /// Gets the number of disposables contained in the CompositeDisposable.
  59. /// </summary>
  60. public int Count
  61. {
  62. get
  63. {
  64. return _count;
  65. }
  66. }
  67. /// <summary>
  68. /// Adds a disposable to the CompositeDisposable or disposes the disposable if the CompositeDisposable is disposed.
  69. /// </summary>
  70. /// <param name="item">Disposable to add.</param>
  71. /// <exception cref="ArgumentNullException"><paramref name="item"/> is null.</exception>
  72. public void Add(IDisposable item)
  73. {
  74. if (item == null)
  75. throw new ArgumentNullException("item");
  76. var shouldDispose = false;
  77. lock (_gate)
  78. {
  79. shouldDispose = _disposed;
  80. if (!_disposed)
  81. {
  82. _disposables.Add(item);
  83. _count++;
  84. }
  85. }
  86. if (shouldDispose)
  87. item.Dispose();
  88. }
  89. /// <summary>
  90. /// Removes and disposes the first occurrence of a disposable from the CompositeDisposable.
  91. /// </summary>
  92. /// <param name="item">Disposable to remove.</param>
  93. /// <returns>true if found; false otherwise.</returns>
  94. /// <exception cref="ArgumentNullException"><paramref name="item"/> is null.</exception>
  95. public bool Remove(IDisposable item)
  96. {
  97. if (item == null)
  98. throw new ArgumentNullException("item");
  99. var shouldDispose = false;
  100. lock (_gate)
  101. {
  102. if (!_disposed)
  103. {
  104. //
  105. // List<T> doesn't shrink the size of the underlying array but does collapse the array
  106. // by copying the tail one position to the left of the removal index. We don't need
  107. // index-based lookup but only ordering for sequential disposal. So, instead of spending
  108. // cycles on the Array.Copy imposed by Remove, we use a null sentinel value. We also
  109. // do manual Swiss cheese detection to shrink the list if there's a lot of holes in it.
  110. //
  111. var i = _disposables.IndexOf(item);
  112. if (i >= 0)
  113. {
  114. shouldDispose = true;
  115. _disposables[i] = null;
  116. _count--;
  117. if (_disposables.Capacity > SHRINK_THRESHOLD && _count < _disposables.Capacity / 2)
  118. {
  119. var old = _disposables;
  120. _disposables = new List<IDisposable>(_disposables.Capacity / 2);
  121. foreach (var d in old)
  122. if (d != null)
  123. _disposables.Add(d);
  124. }
  125. }
  126. }
  127. }
  128. if (shouldDispose)
  129. item.Dispose();
  130. return shouldDispose;
  131. }
  132. /// <summary>
  133. /// Disposes all disposables in the group and removes them from the group.
  134. /// </summary>
  135. public void Dispose()
  136. {
  137. var currentDisposables = default(IDisposable[]);
  138. lock (_gate)
  139. {
  140. if (!_disposed)
  141. {
  142. _disposed = true;
  143. currentDisposables = _disposables.ToArray();
  144. _disposables.Clear();
  145. _count = 0;
  146. }
  147. }
  148. if (currentDisposables != null)
  149. {
  150. foreach (var d in currentDisposables)
  151. if (d != null)
  152. d.Dispose();
  153. }
  154. }
  155. /// <summary>
  156. /// Removes and disposes all disposables from the CompositeDisposable, but does not dispose the CompositeDisposable.
  157. /// </summary>
  158. public void Clear()
  159. {
  160. var currentDisposables = default(IDisposable[]);
  161. lock (_gate)
  162. {
  163. currentDisposables = _disposables.ToArray();
  164. _disposables.Clear();
  165. _count = 0;
  166. }
  167. foreach (var d in currentDisposables)
  168. if (d != null)
  169. d.Dispose();
  170. }
  171. /// <summary>
  172. /// Determines whether the CompositeDisposable contains a specific disposable.
  173. /// </summary>
  174. /// <param name="item">Disposable to search for.</param>
  175. /// <returns>true if the disposable was found; otherwise, false.</returns>
  176. /// <exception cref="ArgumentNullException"><paramref name="item"/> is null.</exception>
  177. public bool Contains(IDisposable item)
  178. {
  179. if (item == null)
  180. throw new ArgumentNullException("item");
  181. lock (_gate)
  182. {
  183. return _disposables.Contains(item);
  184. }
  185. }
  186. /// <summary>
  187. /// Copies the disposables contained in the CompositeDisposable to an array, starting at a particular array index.
  188. /// </summary>
  189. /// <param name="array">Array to copy the contained disposables to.</param>
  190. /// <param name="arrayIndex">Target index at which to copy the first disposable of the group.</param>
  191. /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
  192. /// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than zero. -or - <paramref name="arrayIndex"/> is larger than or equal to the array length.</exception>
  193. public void CopyTo(IDisposable[] array, int arrayIndex)
  194. {
  195. if (array == null)
  196. throw new ArgumentNullException("array");
  197. if (arrayIndex < 0 || arrayIndex >= array.Length)
  198. throw new ArgumentOutOfRangeException("arrayIndex");
  199. lock (_gate)
  200. {
  201. var disArray = new List<IDisposable>();
  202. foreach (var item in _disposables)
  203. {
  204. if (item != null) disArray.Add(item);
  205. }
  206. Array.Copy(disArray.ToArray(), 0, array, arrayIndex, array.Length - arrayIndex);
  207. }
  208. }
  209. /// <summary>
  210. /// Always returns false.
  211. /// </summary>
  212. public bool IsReadOnly
  213. {
  214. get { return false; }
  215. }
  216. /// <summary>
  217. /// Returns an enumerator that iterates through the CompositeDisposable.
  218. /// </summary>
  219. /// <returns>An enumerator to iterate over the disposables.</returns>
  220. public IEnumerator<IDisposable> GetEnumerator()
  221. {
  222. var res = new List<IDisposable>();
  223. lock (_gate)
  224. {
  225. foreach (var d in _disposables)
  226. {
  227. if (d != null) res.Add(d);
  228. }
  229. }
  230. return res.GetEnumerator();
  231. }
  232. /// <summary>
  233. /// Returns an enumerator that iterates through the CompositeDisposable.
  234. /// </summary>
  235. /// <returns>An enumerator to iterate over the disposables.</returns>
  236. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  237. {
  238. return GetEnumerator();
  239. }
  240. /// <summary>
  241. /// Gets a value that indicates whether the object is disposed.
  242. /// </summary>
  243. public bool IsDisposed
  244. {
  245. get { return _disposed; }
  246. }
  247. }
  248. }