浏览代码

Avoid repeating code patterns in the Disposables-namespace. (#548)

Daniel C. Weber 7 年之前
父节点
当前提交
1a5e9baea7

+ 108 - 0
Rx.NET/Source/src/System.Reactive/Disposables/Disposable.cs

@@ -2,6 +2,8 @@
 // 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
 {
     /// <summary>
@@ -27,5 +29,111 @@ namespace System.Reactive.Disposables
 
             return new AnonymousDisposable(dispose);
         }
+
+        /// <summary>
+        /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined.
+        /// </summary>
+        internal static IDisposable GetValue(ref IDisposable fieldRef)
+        {
+            var current = Volatile.Read(ref fieldRef);
+
+            return current == BooleanDisposable.True 
+                ? null
+                : current;
+        }
+
+        /// <summary>
+        /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined.
+        /// </summary>
+        internal static IDisposable GetValueOrDefault(ref IDisposable fieldRef)
+        {
+            var current = Volatile.Read(ref fieldRef);
+
+            return current == BooleanDisposable.True
+                ? DefaultDisposable.Instance
+                : current;
+        }
+
+        internal static bool TrySetSingle(ref IDisposable fieldRef, IDisposable value)
+        {
+            var old = Interlocked.CompareExchange(ref fieldRef, value, null);
+            if (old == null)
+                return true;
+
+            if (old != BooleanDisposable.True)
+                throw new InvalidOperationException(Strings_Core.DISPOSABLE_ALREADY_ASSIGNED);
+
+            value?.Dispose();
+            return false;
+        }
+
+        internal static bool TrySetMultiple(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;
+            }
+        }
+
+        internal static bool TrySetSerial(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;
+            }
+        }
+
+        internal static bool GetIsDisposed(ref IDisposable fieldRef)
+        {
+            // 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 fieldRef) == BooleanDisposable.True;
+        }
+
+        internal static bool TryDispose(ref IDisposable fieldRef)
+        {
+            var old = Interlocked.Exchange(ref fieldRef, BooleanDisposable.True);
+
+            if (old == BooleanDisposable.True)
+                return false;
+
+            old?.Dispose();
+            return true;
+        }
     }
 }

+ 4 - 66
Rx.NET/Source/src/System.Reactive/Disposables/MultipleAssignmentDisposable.cs

@@ -23,16 +23,7 @@ namespace System.Reactive.Disposables
         /// <summary>
         /// Gets a value that indicates whether the object is disposed.
         /// </summary>
-        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;
-            }
-        }
+        public bool IsDisposed => Disposables.Disposable.GetIsDisposed(ref _current);
 
         /// <summary>
         /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined.
@@ -40,46 +31,8 @@ namespace System.Reactive.Disposables
         /// <remarks>If the <see cref="MultipleAssignmentDisposable"/> has already been disposed, assignment to this property causes immediate disposal of the given disposable object.</remarks>
         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;
-                }
-            }
+            get => Disposables.Disposable.GetValueOrDefault(ref _current);
+            set => Disposables.Disposable.TrySetMultiple(ref _current, value);
         }
 
         /// <summary>
@@ -87,22 +40,7 @@ namespace System.Reactive.Disposables
         /// </summary>
         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();
-                }
-            }
+            Disposables.Disposable.TryDispose(ref _current);
         }
     }
 }

+ 4 - 42
Rx.NET/Source/src/System.Reactive/Disposables/SerialDisposable.cs

@@ -23,16 +23,7 @@ namespace System.Reactive.Disposables
         /// <summary>
         /// Gets a value that indicates whether the object is disposed.
         /// </summary>
-        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;
-            }
-        }
+        public bool IsDisposed => Disposables.Disposable.GetIsDisposed(ref _current);
 
         /// <summary>
         /// Gets or sets the underlying disposable.
@@ -40,36 +31,8 @@ namespace System.Reactive.Disposables
         /// <remarks>If the SerialDisposable has already been disposed, assignment to this property causes immediate disposal of the given disposable object. Assigning this property disposes the previous disposable object.</remarks>
         public IDisposable Disposable
         {
-            get
-            {
-                var a = Volatile.Read(ref _current);
-                // Don't leak the DISPOSED sentinel
-                if (a == BooleanDisposable.True)
-                {
-                    a = null;
-                }
-                return a;
-            }
-
-            set
-            {
-                var copy = Volatile.Read(ref _current);
-                for (;;)
-                {
-                    if (copy == BooleanDisposable.True)
-                    {
-                        value?.Dispose();
-                        return;
-                    }
-                    var current = Interlocked.CompareExchange(ref _current, value, copy);
-                    if (current == copy)
-                    {
-                        copy?.Dispose();
-                        return;
-                    }
-                    copy = current;
-                }
-            }
+            get => Disposables.Disposable.GetValue(ref _current);
+            set => Disposables.Disposable.TrySetSerial(ref _current, value);
         }
 
         /// <summary>
@@ -77,8 +40,7 @@ namespace System.Reactive.Disposables
         /// </summary>
         public void Dispose()
         {
-            var old = Interlocked.Exchange(ref _current, BooleanDisposable.True);
-            old?.Dispose();
+            Disposables.Disposable.TryDispose(ref _current);
         }
     }
 }

+ 5 - 35
Rx.NET/Source/src/System.Reactive/Disposables/SingleAssignmentDisposable.cs

@@ -12,7 +12,7 @@ namespace System.Reactive.Disposables
     /// </summary>
     public sealed class SingleAssignmentDisposable : ICancelable
     {
-        private volatile IDisposable _current;
+        private IDisposable _current;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="SingleAssignmentDisposable"/> class.
@@ -24,16 +24,7 @@ namespace System.Reactive.Disposables
         /// <summary>
         /// Gets a value that indicates whether the object is disposed.
         /// </summary>
-        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 _current == BooleanDisposable.True;
-            }
-        }
+        public bool IsDisposed => Disposables.Disposable.GetIsDisposed(ref _current);
 
         /// <summary>
         /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined.
@@ -41,29 +32,8 @@ namespace System.Reactive.Disposables
         /// <exception cref="InvalidOperationException">Thrown if the <see cref="SingleAssignmentDisposable"/> has already been assigned to.</exception>
         public IDisposable Disposable
         {
-            get
-            {
-                var current = _current;
-
-                if (current == BooleanDisposable.True)
-                {
-                    return DefaultDisposable.Instance; // Don't leak the sentinel value.
-                }
-
-                return current;
-            }
-
-            set
-            {
-                var old = Interlocked.CompareExchange(ref _current, value, null);
-                if (old == null)
-                    return;
-
-                if (old != BooleanDisposable.True)
-                    throw new InvalidOperationException(Strings_Core.DISPOSABLE_ALREADY_ASSIGNED);
-
-                value?.Dispose();
-            }
+            get => Disposables.Disposable.GetValueOrDefault(ref _current);
+            set => Disposables.Disposable.TrySetSingle(ref _current, value);
         }
 
         /// <summary>
@@ -71,7 +41,7 @@ namespace System.Reactive.Disposables
         /// </summary>
         public void Dispose()
         {
-            Interlocked.Exchange(ref _current, BooleanDisposable.True)?.Dispose();
+            Disposables.Disposable.TryDispose(ref _current);
         }
     }
 }