// 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.Threading; namespace System.Reactive.Disposables { /// /// Represents a disposable resource whose underlying disposable resource can be swapped for another disposable resource. /// public sealed class MultipleAssignmentDisposable : ICancelable { private IDisposable _current; /// /// Initializes a new instance of the class with no current underlying disposable. /// public MultipleAssignmentDisposable() { } /// /// Gets a value that indicates whether the object is disposed. /// public bool IsDisposed { get { // We use a sentinel value to indicate we've been disposed. This sentinel never leaks // to the outside world (see the Disposable property getter), so no-one can ever assign // this value to us manually. return Volatile.Read(ref _current) == BooleanDisposable.True; } } /// /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined. /// /// If the MutableDisposable has already been disposed, assignment to this property causes immediate disposal of the given disposable object. public IDisposable Disposable { get { var a = Volatile.Read(ref _current); // Don't leak the DISPOSED sentinel if (a == BooleanDisposable.True) { a = DefaultDisposable.Instance; } return a; } set { // Let's read the current value atomically (also prevents reordering). var old = Volatile.Read(ref _current); for (;;) { // If it is the disposed instance, dispose the value. if (old == BooleanDisposable.True) { value?.Dispose(); return; } // Atomically swap in the new value and get back the old. var b = Interlocked.CompareExchange(ref _current, value, old); // If the old and new are the same, the swap was successful and we can quit if (old == b) { return; } // Otherwise, make the old reference the current and retry. old = b; } } } /// /// Disposes the underlying disposable as well as all future replacements. /// public void Dispose() { // Read the current atomically. var a = Volatile.Read(ref _current); // If it is the disposed instance, don't bother further. if (a != BooleanDisposable.True) { // Atomically swap in the disposed instance. a = Interlocked.Exchange(ref _current, BooleanDisposable.True); // It is possible there was a concurrent Dispose call so don't need to call Dispose() // on DISPOSED if (a != BooleanDisposable.True) { a?.Dispose(); } } } } }