CompositeDisposable.cs 10 KB

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