| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the Apache 2.0 License.
- // See the LICENSE file in the project root for more information.
- using System.Collections;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading;
- namespace System.Reactive.Disposables
- {
- /// <summary>
- /// Represents a group of disposable resources that are disposed together.
- /// </summary>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "Backward compat + ideally want to get rid of the ICollection nature of the type.")]
- public sealed class CompositeDisposable : ICollection<IDisposable>, ICancelable
- {
- private readonly object _gate = new object();
- private bool _disposed;
- private List<IDisposable> _disposables;
- private int _count;
- private const int SHRINK_THRESHOLD = 64;
- // Default initial capacity of the _disposables list in case
- // The number of items is not known upfront
- private const int DEFAULT_CAPACITY = 16;
- /// <summary>
- /// Initializes a new instance of the <see cref="CompositeDisposable"/> class with no disposables contained by it initially.
- /// </summary>
- public CompositeDisposable()
- {
- _disposables = new List<IDisposable>();
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="CompositeDisposable"/> class with the specified number of disposables.
- /// </summary>
- /// <param name="capacity">The number of disposables that the new CompositeDisposable can initially store.</param>
- /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than zero.</exception>
- public CompositeDisposable(int capacity)
- {
- if (capacity < 0)
- throw new ArgumentOutOfRangeException(nameof(capacity));
- _disposables = new List<IDisposable>(capacity);
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="CompositeDisposable"/> class from a group of disposables.
- /// </summary>
- /// <param name="disposables">Disposables that will be disposed together.</param>
- /// <exception cref="ArgumentNullException"><paramref name="disposables"/> is <c>null</c>.</exception>
- /// <exception cref="ArgumentException">Any of the disposables in the <paramref name="disposables"/> collection is <c>null</c>.</exception>
- public CompositeDisposable(params IDisposable[] disposables)
- {
- if (disposables == null)
- {
- throw new ArgumentNullException(nameof(disposables));
- }
- Init(disposables, disposables.Length);
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="CompositeDisposable"/> class from a group of disposables.
- /// </summary>
- /// <param name="disposables">Disposables that will be disposed together.</param>
- /// <exception cref="ArgumentNullException"><paramref name="disposables"/> is <c>null</c>.</exception>
- /// <exception cref="ArgumentException">Any of the disposables in the <paramref name="disposables"/> collection is <c>null</c>.</exception>
- public CompositeDisposable(IEnumerable<IDisposable> disposables)
- {
- if (disposables == null)
- throw new ArgumentNullException(nameof(disposables));
- // If the disposables is a collection, get its size
- // and use it as a capacity hint for the copy.
- if (disposables is ICollection<IDisposable> c)
- {
- Init(disposables, c.Count);
- }
- else
- {
- // Unknown sized disposables, use the default capacity hint
- Init(disposables, DEFAULT_CAPACITY);
- }
- }
- /// <summary>
- /// Initialize the inner disposable list and count fields.
- /// </summary>
- /// <param name="disposables">The enumerable sequence of disposables.</param>
- /// <param name="capacityHint">The number of items expected from <paramref name="disposables"/></param>
- private void Init(IEnumerable<IDisposable> disposables, int capacityHint)
- {
- var list = new List<IDisposable>(capacityHint);
- // do the copy and null-check in one step to avoid a
- // second loop for just checking for null items
- foreach (var d in disposables)
- {
- if (d == null)
- {
- throw new ArgumentException(Strings_Core.DISPOSABLES_CANT_CONTAIN_NULL, nameof(disposables));
- }
- list.Add(d);
- }
- _disposables = list;
- // _count can be read by other threads and thus should be properly visible
- // also releases the _disposables contents so it becomes thread-safe
- Volatile.Write(ref _count, _disposables.Count);
- }
- /// <summary>
- /// Gets the number of disposables contained in the <see cref="CompositeDisposable"/>.
- /// </summary>
- public int Count => Volatile.Read(ref _count);
- /// <summary>
- /// Adds a disposable to the <see cref="CompositeDisposable"/> or disposes the disposable if the <see cref="CompositeDisposable"/> is disposed.
- /// </summary>
- /// <param name="item">Disposable to add.</param>
- /// <exception cref="ArgumentNullException"><paramref name="item"/> is <c>null</c>.</exception>
- public void Add(IDisposable item)
- {
- if (item == null)
- throw new ArgumentNullException(nameof(item));
- lock (_gate)
- {
- if (!_disposed)
- {
- _disposables.Add(item);
- // If read atomically outside the lock, it should be written atomically inside
- // the plain read on _count is fine here because manipulation always happens
- // from inside a lock.
- Volatile.Write(ref _count, _count + 1);
- return;
- }
- }
- item.Dispose();
- }
- /// <summary>
- /// Removes and disposes the first occurrence of a disposable from the <see cref="CompositeDisposable"/>.
- /// </summary>
- /// <param name="item">Disposable to remove.</param>
- /// <returns>true if found; false otherwise.</returns>
- /// <exception cref="ArgumentNullException"><paramref name="item"/> is <c>null</c>.</exception>
- public bool Remove(IDisposable item)
- {
- if (item == null)
- throw new ArgumentNullException(nameof(item));
- lock (_gate)
- {
- // this composite was already disposed and if the item was in there
- // it has been already removed/disposed
- if (_disposed)
- {
- return false;
- }
- //
- // List<T> doesn't shrink the size of the underlying array but does collapse the array
- // by copying the tail one position to the left of the removal index. We don't need
- // index-based lookup but only ordering for sequential disposal. So, instead of spending
- // cycles on the Array.Copy imposed by Remove, we use a null sentinel value. We also
- // do manual Swiss cheese detection to shrink the list if there's a lot of holes in it.
- //
- // read fields as infrequently as possible
- var current = _disposables;
- var i = current.IndexOf(item);
- if (i < 0)
- {
- // not found, just return
- return false;
- }
- current[i] = null;
- if (current.Capacity > SHRINK_THRESHOLD && _count < current.Capacity / 2)
- {
- var fresh = new List<IDisposable>(current.Capacity / 2);
- foreach (var d in current)
- {
- if (d != null)
- {
- fresh.Add(d);
- }
- }
- _disposables = fresh;
- }
- // make sure the Count property sees an atomic update
- Volatile.Write(ref _count, _count - 1);
- }
- // if we get here, the item was found and removed from the list
- // just dispose it and report success
- item.Dispose();
- return true;
- }
- /// <summary>
- /// Disposes all disposables in the group and removes them from the group.
- /// </summary>
- public void Dispose()
- {
- var currentDisposables = default(List<IDisposable>);
- lock (_gate)
- {
- if (!_disposed)
- {
- currentDisposables = _disposables;
- // nulling out the reference is faster no risk to
- // future Add/Remove because _disposed will be true
- // and thus _disposables won't be touched again.
- _disposables = null;
- Volatile.Write(ref _count, 0);
- Volatile.Write(ref _disposed, true);
- }
- }
- if (currentDisposables != null)
- {
- foreach (var d in currentDisposables)
- {
- d?.Dispose();
- }
- }
- }
- /// <summary>
- /// Removes and disposes all disposables from the <see cref="CompositeDisposable"/>, but does not dispose the <see cref="CompositeDisposable"/>.
- /// </summary>
- public void Clear()
- {
- var previousDisposables = default(IDisposable[]);
- lock (_gate)
- {
- // disposed composites are always clear
- if (_disposed)
- {
- return;
- }
- var current = _disposables;
- previousDisposables = current.ToArray();
- current.Clear();
- Volatile.Write(ref _count, 0);
- }
- foreach (var d in previousDisposables)
- {
- d?.Dispose();
- }
- }
- /// <summary>
- /// Determines whether the <see cref="CompositeDisposable"/> contains a specific disposable.
- /// </summary>
- /// <param name="item">Disposable to search for.</param>
- /// <returns>true if the disposable was found; otherwise, false.</returns>
- /// <exception cref="ArgumentNullException"><paramref name="item"/> is <c>null</c>.</exception>
- public bool Contains(IDisposable item)
- {
- if (item == null)
- throw new ArgumentNullException(nameof(item));
- lock (_gate)
- {
- if (_disposed)
- {
- return false;
- }
- return _disposables.Contains(item);
- }
- }
- /// <summary>
- /// Copies the disposables contained in the <see cref="CompositeDisposable"/> to an array, starting at a particular array index.
- /// </summary>
- /// <param name="array">Array to copy the contained disposables to.</param>
- /// <param name="arrayIndex">Target index at which to copy the first disposable of the group.</param>
- /// <exception cref="ArgumentNullException"><paramref name="array"/> is <c>null</c>.</exception>
- /// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than zero. -or - <paramref name="arrayIndex"/> is larger than or equal to the array length.</exception>
- public void CopyTo(IDisposable[] array, int arrayIndex)
- {
- if (array == null)
- throw new ArgumentNullException(nameof(array));
- if (arrayIndex < 0 || arrayIndex >= array.Length)
- throw new ArgumentOutOfRangeException(nameof(arrayIndex));
- lock (_gate)
- {
- // disposed composites are always empty
- if (_disposed)
- {
- return;
- }
- if (arrayIndex + _count > array.Length)
- {
- // there is not enough space beyond arrayIndex
- // to accommodate all _count disposables in this composite
- throw new ArgumentOutOfRangeException(nameof(arrayIndex));
- }
- var i = arrayIndex;
- foreach (var d in _disposables)
- {
- if (d != null)
- {
- array[i++] = d;
- }
- }
- }
- }
- /// <summary>
- /// Always returns false.
- /// </summary>
- public bool IsReadOnly => false;
- /// <summary>
- /// Returns an enumerator that iterates through the <see cref="CompositeDisposable"/>.
- /// </summary>
- /// <returns>An enumerator to iterate over the disposables.</returns>
- public IEnumerator<IDisposable> GetEnumerator()
- {
- lock (_gate)
- {
- if (_disposed || _count == 0)
- {
- return EMPTY_ENUMERATOR;
- }
- // the copy is unavoidable but the creation
- // of an outer IEnumerable is avoidable
- return new CompositeEnumerator(_disposables.ToArray());
- }
- }
- /// <summary>
- /// Returns an enumerator that iterates through the <see cref="CompositeDisposable"/>.
- /// </summary>
- /// <returns>An enumerator to iterate over the disposables.</returns>
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
- /// <summary>
- /// Gets a value that indicates whether the object is disposed.
- /// </summary>
- public bool IsDisposed => Volatile.Read(ref _disposed);
- /// <summary>
- /// An empty enumerator for the <see cref="GetEnumerator"/>
- /// method to avoid allocation on disposed or empty composites.
- /// </summary>
- static readonly CompositeEnumerator EMPTY_ENUMERATOR =
- new CompositeEnumerator(new IDisposable[0]);
- /// <summary>
- /// An enumerator for an array of disposables.
- /// </summary>
- sealed class CompositeEnumerator : IEnumerator<IDisposable>
- {
- readonly IDisposable[] disposables;
- int index;
- public CompositeEnumerator(IDisposable[] disposables)
- {
- this.disposables = disposables;
- this.index = -1;
- }
- public IDisposable Current => disposables[index];
- object IEnumerator.Current => disposables[index];
- public void Dispose()
- {
- // Avoid retention of the referenced disposables
- // beyond the lifecycle of the enumerator.
- // Not sure if this happens by default to
- // generic array enumerators though.
- var disposables = this.disposables;
- Array.Clear(disposables, 0, disposables.Length);
- }
- public bool MoveNext()
- {
- var disposables = this.disposables;
- for (; ; )
- {
- var idx = ++index;
- if (idx >= disposables.Length)
- {
- return false;
- }
- // inlined that filter for null elements
- if (disposables[idx] != null)
- {
- return true;
- }
- }
- }
- public void Reset()
- {
- index = -1;
- }
- }
- }
- }
|