// 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.Generic; using System.Linq; namespace System.Reactive.Disposables { /// /// Represents a group of disposable resources that are disposed together. /// [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, 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(nameof(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. /// Any of the disposables in the collection is null. public CompositeDisposable(params IDisposable[] disposables) : this((IEnumerable)disposables) { } /// /// Initializes a new instance of the class from a group of disposables. /// /// Disposables that will be disposed together. /// is null. /// Any of the disposables in the collection is null. public CompositeDisposable(IEnumerable disposables) { if (disposables == null) throw new ArgumentNullException(nameof(disposables)); _disposables = new List(disposables); // // Doing this on the list to avoid duplicate enumeration of disposables. // if (_disposables.Contains(null)) throw new ArgumentException(Strings_Core.DISPOSABLES_CANT_CONTAIN_NULL, nameof(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(nameof(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(nameof(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(nameof(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(nameof(array)); if (arrayIndex < 0 || arrayIndex >= array.Length) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); lock (_gate) { Array.Copy(_disposables.Where(d => d != null).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 = default(IEnumerable); lock (_gate) { res = _disposables.Where(d => d != null).ToList(); } 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; } } } }