CompositeDisposable.cs 11 KB

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