// 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.Diagnostics.CodeAnalysis; using System.Threading; namespace System.Reactive.Disposables { internal enum TrySetSingleResult { Success, AlreadyAssigned, Disposed } public static partial class Disposable { /// /// Gets the value stored in or a null if /// was already disposed. /// internal static IDisposable? GetValue([NotNullIfNotNull(nameof(fieldRef))] /*in*/ ref IDisposable? fieldRef) { var current = Volatile.Read(ref fieldRef); return current == BooleanDisposable.True ? null : current; } /// /// Gets the value stored in or a no-op-Disposable if /// was already disposed. /// [return: NotNullIfNotNull(nameof(fieldRef))] internal static IDisposable? GetValueOrDefault([NotNullIfNotNull(nameof(fieldRef))] /*in*/ ref IDisposable? fieldRef) { var current = Volatile.Read(ref fieldRef); return current == BooleanDisposable.True ? EmptyDisposable.Instance : current; } /// /// Tries to assign to . /// /// A value indicating the outcome of the operation. internal static TrySetSingleResult TrySetSingle([NotNullIfNotNull(nameof(value))] ref IDisposable? fieldRef, IDisposable? value) { var old = Interlocked.CompareExchange(ref fieldRef, value, null); if (old == null) { return TrySetSingleResult.Success; } if (old != BooleanDisposable.True) { return TrySetSingleResult.AlreadyAssigned; } value?.Dispose(); return TrySetSingleResult.Disposed; } /// /// Tries to assign to . If /// is not disposed and is assigned a different value, it will not be disposed. /// /// true if was successfully assigned to . /// false has been disposed. internal static bool TrySetMultiple([NotNullIfNotNull(nameof(value))] ref IDisposable? fieldRef, IDisposable? value) { // Let's read the current value atomically (also prevents reordering). var old = Volatile.Read(ref fieldRef); for (; ; ) { // If it is the disposed instance, dispose the value. if (old == BooleanDisposable.True) { value?.Dispose(); return false; } // Atomically swap in the new value and get back the old. var b = Interlocked.CompareExchange(ref fieldRef, value, old); // If the old and new are the same, the swap was successful and we can quit if (old == b) { return true; } // Otherwise, make the old reference the current and retry. old = b; } } /// /// Tries to assign to . If /// is not disposed and is assigned a different value, it will be disposed. /// /// true if was successfully assigned to . /// false has been disposed. internal static bool TrySetSerial([NotNullIfNotNull(nameof(value))] ref IDisposable? fieldRef, IDisposable? value) { var copy = Volatile.Read(ref fieldRef); for (; ; ) { if (copy == BooleanDisposable.True) { value?.Dispose(); return false; } var current = Interlocked.CompareExchange(ref fieldRef, value, copy); if (current == copy) { copy?.Dispose(); return true; } copy = current; } } /// /// Disposes . /// internal static void Dispose([NotNullIfNotNull(nameof(fieldRef))] ref IDisposable? fieldRef) { var old = Interlocked.Exchange(ref fieldRef, BooleanDisposable.True); if (old != BooleanDisposable.True) { old?.Dispose(); } } } }