// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT License. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Threading; namespace System.Reactive.Disposables { /// /// Represents a group of disposable resources that are disposed together. /// public abstract class StableCompositeDisposable : ICancelable { /// /// Creates a new group containing two disposable resources that are disposed together. /// /// The first disposable resource to add to the group. /// The second disposable resource to add to the group. /// Group of disposable resources that are disposed together. public static ICancelable Create(IDisposable disposable1, IDisposable disposable2) { if (disposable1 == null) { throw new ArgumentNullException(nameof(disposable1)); } if (disposable2 == null) { throw new ArgumentNullException(nameof(disposable2)); } return new Binary(disposable1, disposable2); } /// /// Creates a new group of disposable resources that are disposed together. /// /// Disposable resources to add to the group. /// Group of disposable resources that are disposed together. public static ICancelable Create(params IDisposable[] disposables) { if (disposables == null) { throw new ArgumentNullException(nameof(disposables)); } return new NAryArray(disposables); } /// /// Creates a group of disposable resources that are disposed together /// and without copying or checking for nulls inside the group. /// /// The array of disposables that is trusted /// to not contain nulls and gives no need to defensively copy it. /// Group of disposable resources that are disposed together. internal static ICancelable CreateTrusted(params IDisposable[] disposables) { return new NAryTrustedArray(disposables); } /// /// Creates a new group of disposable resources that are disposed together. /// /// Disposable resources to add to the group. /// Group of disposable resources that are disposed together. public static ICancelable Create(IEnumerable disposables) { if (disposables == null) { throw new ArgumentNullException(nameof(disposables)); } return new NAryEnumerable(disposables); } /// /// Disposes all disposables in the group. /// public abstract void Dispose(); /// /// Gets a value that indicates whether the object is disposed. /// public abstract bool IsDisposed { get; } private sealed class Binary : StableCompositeDisposable { private IDisposable? _disposable1; private IDisposable? _disposable2; public Binary(IDisposable disposable1, IDisposable disposable2) { Volatile.Write(ref _disposable1, disposable1); Volatile.Write(ref _disposable2, disposable2); } public override bool IsDisposed => Disposable.GetIsDisposed(ref _disposable1); public override void Dispose() { Disposable.Dispose(ref _disposable1); Disposable.Dispose(ref _disposable2); } } private sealed class NAryEnumerable : StableCompositeDisposable { private volatile List? _disposables; public NAryEnumerable(IEnumerable 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)); } } public override bool IsDisposed => _disposables == null; public override void Dispose() { var old = Interlocked.Exchange(ref _disposables, null); if (old != null) { foreach (var d in old) { d.Dispose(); } } } } private sealed class NAryArray : StableCompositeDisposable { private IDisposable[]? _disposables; public NAryArray(IDisposable[] disposables) { if (Array.IndexOf(disposables, null!) != -1) { throw new ArgumentException(Strings_Core.DISPOSABLES_CANT_CONTAIN_NULL, nameof(disposables)); } var n = disposables.Length; var ds = new IDisposable[n]; Array.Copy(disposables, 0, ds, 0, n); Volatile.Write(ref _disposables, ds); } public override bool IsDisposed => Volatile.Read(ref _disposables) == null; public override void Dispose() { var old = Interlocked.Exchange(ref _disposables, null); if (old != null) { foreach (var d in old) { d.Dispose(); } } } } /// /// A stable composite that doesn't do defensive copy of /// the input disposable array nor checks it for null. /// private sealed class NAryTrustedArray : StableCompositeDisposable { private IDisposable[]? _disposables; public NAryTrustedArray(IDisposable[] disposables) { Volatile.Write(ref _disposables, disposables); } public override bool IsDisposed => Volatile.Read(ref _disposables) == null; public override void Dispose() { var old = Interlocked.Exchange(ref _disposables, null); if (old != null) { foreach (var d in old) { d.Dispose(); } } } } } }