using System; using System.Collections.Generic; // using System.Linq; do not use LINQ using System.Text; namespace UniRx { // copy, modified from Rx Official public sealed class CompositeDisposable : ICollection, IDisposable, ICancelable { private readonly object _gate = new object(); private bool _disposed; private List _disposables; private int _count; private const int SHRINK_THRESHOLD = 64; /// /// Initializes a new instance of the class with no disposables contained by it initially. /// public CompositeDisposable() { _disposables = new List(); } /// /// Initializes a new instance of the class with the specified number of disposables. /// /// The number of disposables that the new CompositeDisposable can initially store. /// is less than zero. public CompositeDisposable(int capacity) { if (capacity < 0) throw new ArgumentOutOfRangeException("capacity"); _disposables = new List(capacity); } /// /// Initializes a new instance of the class from a group of disposables. /// /// Disposables that will be disposed together. /// is null. public CompositeDisposable(params IDisposable[] disposables) { if (disposables == null) throw new ArgumentNullException("disposables"); _disposables = new List(disposables); _count = _disposables.Count; } /// /// Initializes a new instance of the class from a group of disposables. /// /// Disposables that will be disposed together. /// is null. public CompositeDisposable(IEnumerable disposables) { if (disposables == null) throw new ArgumentNullException("disposables"); _disposables = new List(disposables); _count = _disposables.Count; } /// /// Gets the number of disposables contained in the CompositeDisposable. /// public int Count { get { return _count; } } /// /// Adds a disposable to the CompositeDisposable or disposes the disposable if the CompositeDisposable is disposed. /// /// Disposable to add. /// is null. public void Add(IDisposable item) { if (item == null) throw new ArgumentNullException("item"); var shouldDispose = false; lock (_gate) { shouldDispose = _disposed; if (!_disposed) { _disposables.Add(item); _count++; } } if (shouldDispose) item.Dispose(); } /// /// Removes and disposes the first occurrence of a disposable from the CompositeDisposable. /// /// Disposable to remove. /// true if found; false otherwise. /// is null. public bool Remove(IDisposable item) { if (item == null) throw new ArgumentNullException("item"); var shouldDispose = false; lock (_gate) { if (!_disposed) { // // List 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. // var i = _disposables.IndexOf(item); if (i >= 0) { shouldDispose = true; _disposables[i] = null; _count--; if (_disposables.Capacity > SHRINK_THRESHOLD && _count < _disposables.Capacity / 2) { var old = _disposables; _disposables = new List(_disposables.Capacity / 2); foreach (var d in old) if (d != null) _disposables.Add(d); } } } } if (shouldDispose) item.Dispose(); return shouldDispose; } /// /// Disposes all disposables in the group and removes them from the group. /// public void Dispose() { var currentDisposables = default(IDisposable[]); lock (_gate) { if (!_disposed) { _disposed = true; currentDisposables = _disposables.ToArray(); _disposables.Clear(); _count = 0; } } if (currentDisposables != null) { foreach (var d in currentDisposables) if (d != null) d.Dispose(); } } /// /// Removes and disposes all disposables from the CompositeDisposable, but does not dispose the CompositeDisposable. /// public void Clear() { var currentDisposables = default(IDisposable[]); lock (_gate) { currentDisposables = _disposables.ToArray(); _disposables.Clear(); _count = 0; } foreach (var d in currentDisposables) if (d != null) d.Dispose(); } /// /// Determines whether the CompositeDisposable contains a specific disposable. /// /// Disposable to search for. /// true if the disposable was found; otherwise, false. /// is null. public bool Contains(IDisposable item) { if (item == null) throw new ArgumentNullException("item"); lock (_gate) { return _disposables.Contains(item); } } /// /// Copies the disposables contained in the CompositeDisposable to an array, starting at a particular array index. /// /// Array to copy the contained disposables to. /// Target index at which to copy the first disposable of the group. /// is null. /// is less than zero. -or - is larger than or equal to the array length. public void CopyTo(IDisposable[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException("array"); if (arrayIndex < 0 || arrayIndex >= array.Length) throw new ArgumentOutOfRangeException("arrayIndex"); lock (_gate) { var disArray = new List(); foreach (var item in _disposables) { if (item != null) disArray.Add(item); } Array.Copy(disArray.ToArray(), 0, array, arrayIndex, array.Length - arrayIndex); } } /// /// Always returns false. /// public bool IsReadOnly { get { return false; } } /// /// Returns an enumerator that iterates through the CompositeDisposable. /// /// An enumerator to iterate over the disposables. public IEnumerator GetEnumerator() { var res = new List(); lock (_gate) { foreach (var d in _disposables) { if (d != null) res.Add(d); } } return res.GetEnumerator(); } /// /// Returns an enumerator that iterates through the CompositeDisposable. /// /// An enumerator to iterate over the disposables. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Gets a value that indicates whether the object is disposed. /// public bool IsDisposed { get { return _disposed; } } } }