CompositeDisposable.cs 11 KB

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