diff options
Diffstat (limited to 'Rx.NET/System.Reactive.Core/Reactive/Disposables')
12 files changed, 1029 insertions, 0 deletions
diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/AnonymousDisposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/AnonymousDisposable.cs new file mode 100644 index 0000000..f31bbf4 --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/AnonymousDisposable.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Represents an Action-based disposable. + /// </summary> + internal sealed class AnonymousDisposable : ICancelable + { + private volatile Action _dispose; + + /// <summary> + /// Constructs a new disposable with the given action used for disposal. + /// </summary> + /// <param name="dispose">Disposal action which will be run upon calling Dispose.</param> + public AnonymousDisposable(Action dispose) + { + System.Diagnostics.Debug.Assert(dispose != null); + + _dispose = dispose; + } + + /// <summary> + /// Gets a value that indicates whether the object is disposed. + /// </summary> + public bool IsDisposed + { + get { return _dispose == null; } + } + + /// <summary> + /// Calls the disposal action if and only if the current instance hasn't been disposed yet. + /// </summary> + public void Dispose() + { +#pragma warning disable 0420 + var dispose = Interlocked.Exchange(ref _dispose, null); +#pragma warning restore 0420 + if (dispose != null) + { + dispose(); + } + } + } +} diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/BooleanDisposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/BooleanDisposable.cs new file mode 100644 index 0000000..1f1a21f --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/BooleanDisposable.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Represents a disposable resource that can be checked for disposal status. + /// </summary> + public sealed class BooleanDisposable : ICancelable + { + // Keep internal! This is used as sentinel in other IDisposable implementations to detect disposal and + // should never be exposed to user code in order to prevent users from swapping in the sentinel. Have + // a look at the code in e.g. SingleAssignmentDisposable for usage patterns. + internal static readonly BooleanDisposable True = new BooleanDisposable(true); + + private volatile bool _isDisposed; + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.BooleanDisposable"/> class. + /// </summary> + public BooleanDisposable() + { + } + + private BooleanDisposable(bool isDisposed) + { + _isDisposed = isDisposed; + } + + /// <summary> + /// Gets a value that indicates whether the object is disposed. + /// </summary> + public bool IsDisposed + { + get { return _isDisposed; } + } + + /// <summary> + /// Sets the status to disposed, which can be observer through the <see cref="IsDisposed"/> property. + /// </summary> + public void Dispose() + { + _isDisposed = true; + } + } +} diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/CancellationDisposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/CancellationDisposable.cs new file mode 100644 index 0000000..5c67511 --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/CancellationDisposable.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +#if !NO_TPL +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Represents a disposable resource that has an associated <seealso cref="T:System.Threading.CancellationToken"/> that will be set to the cancellation requested state upon disposal. + /// </summary> + public sealed class CancellationDisposable : ICancelable + { + private readonly CancellationTokenSource _cts; + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.CancellationDisposable"/> class that uses an existing <seealso cref="T:System.Threading.CancellationTokenSource"/>. + /// </summary> + /// <param name="cts"><seealso cref="T:System.Threading.CancellationTokenSource"/> used for cancellation.</param> + /// <exception cref="ArgumentNullException"><paramref name="cts"/> is null.</exception> + public CancellationDisposable(CancellationTokenSource cts) + { + if (cts == null) + throw new ArgumentNullException("cts"); + + _cts = cts; + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.CancellationDisposable"/> class that uses a new <seealso cref="T:System.Threading.CancellationTokenSource"/>. + /// </summary> + public CancellationDisposable() + : this(new CancellationTokenSource()) + { + } + + /// <summary> + /// Gets the <see cref="T:System.Threading.CancellationToken"/> used by this CancellationDisposable. + /// </summary> + public CancellationToken Token + { + get { return _cts.Token; } + } + + /// <summary> + /// Cancels the underlying <seealso cref="T:System.Threading.CancellationTokenSource"/>. + /// </summary> + public void Dispose() + { + _cts.Cancel(); + } + + /// <summary> + /// Gets a value that indicates whether the object is disposed. + /// </summary> + public bool IsDisposed + { + get { return _cts.IsCancellationRequested; } + } + } +} +#endif
\ No newline at end of file diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/CompositeDisposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/CompositeDisposable.cs new file mode 100644 index 0000000..6416cff --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/CompositeDisposable.cs @@ -0,0 +1,276 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Represents a group of disposable resources that are disposed together. + /// </summary> + [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<IDisposable>, ICancelable + { + private readonly object _gate = new object(); + + private bool _disposed; + private List<IDisposable> _disposables; + private int _count; + private const int SHRINK_THRESHOLD = 64; + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.CompositeDisposable"/> class with no disposables contained by it initially. + /// </summary> + public CompositeDisposable() + { + _disposables = new List<IDisposable>(); + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.CompositeDisposable"/> class with the specified number of disposables. + /// </summary> + /// <param name="capacity">The number of disposables that the new CompositeDisposable can initially store.</param> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than zero.</exception> + public CompositeDisposable(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + + _disposables = new List<IDisposable>(capacity); + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.CompositeDisposable"/> class from a group of disposables. + /// </summary> + /// <param name="disposables">Disposables that will be disposed together.</param> + /// <exception cref="ArgumentNullException"><paramref name="disposables"/> is null.</exception> + public CompositeDisposable(params IDisposable[] disposables) + { + if (disposables == null) + throw new ArgumentNullException("disposables"); + + _disposables = new List<IDisposable>(disposables); + _count = _disposables.Count; + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.CompositeDisposable"/> class from a group of disposables. + /// </summary> + /// <param name="disposables">Disposables that will be disposed together.</param> + /// <exception cref="ArgumentNullException"><paramref name="disposables"/> is null.</exception> + public CompositeDisposable(IEnumerable<IDisposable> disposables) + { + if (disposables == null) + throw new ArgumentNullException("disposables"); + + _disposables = new List<IDisposable>(disposables); + _count = _disposables.Count; + } + + /// <summary> + /// Gets the number of disposables contained in the CompositeDisposable. + /// </summary> + public int Count + { + get + { + return _count; + } + } + + /// <summary> + /// Adds a disposable to the CompositeDisposable or disposes the disposable if the CompositeDisposable is disposed. + /// </summary> + /// <param name="item">Disposable to add.</param> + /// <exception cref="ArgumentNullException"><paramref name="item"/> is null.</exception> + public void Add(IDisposable item) + { + if (item == null) + throw new ArgumentNullException("item"); + + var shouldDispose = false; + lock (_gate) + { + shouldDispose = _disposed; + if (!_disposed) + { + _disposables.Add(item); + _count++; + } + } + if (shouldDispose) + item.Dispose(); + } + + /// <summary> + /// Removes and disposes the first occurrence of a disposable from the CompositeDisposable. + /// </summary> + /// <param name="item">Disposable to remove.</param> + /// <returns>true if found; false otherwise.</returns> + /// <exception cref="ArgumentNullException"><paramref name="item"/> is null.</exception> + public bool Remove(IDisposable item) + { + if (item == null) + throw new ArgumentNullException("item"); + + var shouldDispose = false; + + lock (_gate) + { + if (!_disposed) + { + // + // List<T> 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<IDisposable>(_disposables.Capacity / 2); + + foreach (var d in old) + if (d != null) + _disposables.Add(d); + } + } + } + } + + if (shouldDispose) + item.Dispose(); + + return shouldDispose; + } + + /// <summary> + /// Disposes all disposables in the group and removes them from the group. + /// </summary> + 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(); + } + } + + /// <summary> + /// Removes and disposes all disposables from the CompositeDisposable, but does not dispose the CompositeDisposable. + /// </summary> + 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(); + } + + /// <summary> + /// Determines whether the CompositeDisposable contains a specific disposable. + /// </summary> + /// <param name="item">Disposable to search for.</param> + /// <returns>true if the disposable was found; otherwise, false.</returns> + /// <exception cref="ArgumentNullException"><paramref name="item"/> is null.</exception> + public bool Contains(IDisposable item) + { + if (item == null) + throw new ArgumentNullException("item"); + + lock (_gate) + { + return _disposables.Contains(item); + } + } + + /// <summary> + /// Copies the disposables contained in the CompositeDisposable to an array, starting at a particular array index. + /// </summary> + /// <param name="array">Array to copy the contained disposables to.</param> + /// <param name="arrayIndex">Target index at which to copy the first disposable of the group.</param> + /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than zero. -or - <paramref name="arrayIndex"/> is larger than or equal to the array length.</exception> + public void CopyTo(IDisposable[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("array"); + if (arrayIndex < 0 || arrayIndex >= array.Length) + throw new ArgumentOutOfRangeException("arrayIndex"); + + lock (_gate) + { + Array.Copy(_disposables.Where(d => d != null).ToArray(), 0, array, arrayIndex, array.Length - arrayIndex); + } + } + + /// <summary> + /// Always returns false. + /// </summary> + public bool IsReadOnly + { + get { return false; } + } + + /// <summary> + /// Returns an enumerator that iterates through the CompositeDisposable. + /// </summary> + /// <returns>An enumerator to iterate over the disposables.</returns> + public IEnumerator<IDisposable> GetEnumerator() + { + var res = default(IEnumerable<IDisposable>); + + lock (_gate) + { + res = _disposables.Where(d => d != null).ToList(); + } + + return res.GetEnumerator(); + } + + /// <summary> + /// Returns an enumerator that iterates through the CompositeDisposable. + /// </summary> + /// <returns>An enumerator to iterate over the disposables.</returns> + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// <summary> + /// Gets a value that indicates whether the object is disposed. + /// </summary> + public bool IsDisposed + { + get { return _disposed; } + } + } +} diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/ContextDisposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/ContextDisposable.cs new file mode 100644 index 0000000..ebe3479 --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/ContextDisposable.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +#if !NO_SYNCCTX +using System.Reactive.Concurrency; +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Represents a disposable resource whose disposal invocation will be posted to the specified <seealso cref="T:System.Threading.SynchronizationContext"/>. + /// </summary> + public sealed class ContextDisposable : ICancelable + { + private readonly SynchronizationContext _context; + private volatile IDisposable _disposable; + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.ContextDisposable"/> class that uses the specified <see cref="T:System.Threading.SynchronizationContext"/> on which to dispose the specified disposable resource. + /// </summary> + /// <param name="context">Context to perform disposal on.</param> + /// <param name="disposable">Disposable whose Dispose operation to run on the given synchronization context.</param> + /// <exception cref="ArgumentNullException"><paramref name="context"/> or <paramref name="disposable"/> is null.</exception> + public ContextDisposable(SynchronizationContext context, IDisposable disposable) + { + if (context == null) + throw new ArgumentNullException("context"); + if (disposable == null) + throw new ArgumentNullException("disposable"); + + _context = context; + _disposable = disposable; + } + + /// <summary> + /// Gets the provided <see cref="T:System.Threading.SynchronizationContext"/>. + /// </summary> + public SynchronizationContext Context + { + get { return _context; } + } + + /// <summary> + /// Gets a value that indicates whether the object is disposed. + /// </summary> + public bool IsDisposed + { + get { return _disposable == BooleanDisposable.True; } + } + + /// <summary> + /// Disposes the underlying disposable on the provided <see cref="T:System.Threading.SynchronizationContext"/>. + /// </summary> + public void Dispose() + { +#pragma warning disable 0420 + var disposable = Interlocked.Exchange(ref _disposable, BooleanDisposable.True); +#pragma warning restore 0420 + + if (disposable != BooleanDisposable.True) + { + _context.PostWithStartComplete(d => d.Dispose(), disposable); + } + } + } +} +#endif
\ No newline at end of file diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/DefaultDisposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/DefaultDisposable.cs new file mode 100644 index 0000000..6f643c3 --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/DefaultDisposable.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Represents a disposable that does nothing on disposal. + /// </summary> + internal sealed class DefaultDisposable : IDisposable + { + /// <summary> + /// Singleton default disposable. + /// </summary> + public static readonly DefaultDisposable Instance = new DefaultDisposable(); + + private DefaultDisposable() + { + } + + /// <summary> + /// Does nothing. + /// </summary> + public void Dispose() + { + // no op + } + } +} diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/Disposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/Disposable.cs new file mode 100644 index 0000000..fc77f3b --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/Disposable.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Provides a set of static methods for creating Disposables. + /// </summary> + public static class Disposable + { + /// <summary> + /// Gets the disposable that does nothing when disposed. + /// </summary> + public static IDisposable Empty + { + get { return DefaultDisposable.Instance; } + } + + /// <summary> + /// Creates a disposable object that invokes the specified action when disposed. + /// </summary> + /// <param name="dispose">Action to run during the first call to <see cref="IDisposable.Dispose"/>. The action is guaranteed to be run at most once.</param> + /// <returns>The disposable object that runs the given action upon disposal.</returns> + /// <exception cref="ArgumentNullException"><paramref name="dispose"/> is null.</exception> + public static IDisposable Create(Action dispose) + { + if (dispose == null) + throw new ArgumentNullException("dispose"); + + return new AnonymousDisposable(dispose); + } + } +} diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/MultipleAssignmentDisposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/MultipleAssignmentDisposable.cs new file mode 100644 index 0000000..0fcc4dc --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/MultipleAssignmentDisposable.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Represents a disposable resource whose underlying disposable resource can be swapped for another disposable resource. + /// </summary> + public sealed class MultipleAssignmentDisposable : ICancelable + { + private readonly object _gate = new object(); + private IDisposable _current; + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.MultipleAssignmentDisposable"/> class with no current underlying disposable. + /// </summary> + public MultipleAssignmentDisposable() + { + } + + /// <summary> + /// Gets a value that indicates whether the object is disposed. + /// </summary> + public bool IsDisposed + { + get + { + lock (_gate) + { + // 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; + } + } + } + + /// <summary> + /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined. + /// </summary> + /// <remarks>If the MutableDisposable has already been disposed, assignment to this property causes immediate disposal of the given disposable object.</remarks> + public IDisposable Disposable + { + get + { + lock (_gate) + { + if (_current == BooleanDisposable.True) + return DefaultDisposable.Instance; // Don't leak the sentinel value. + + return _current; + } + } + + set + { + var shouldDispose = false; + lock (_gate) + { + shouldDispose = IsDisposed; + if (!shouldDispose) + { + _current = value; + } + } + if (shouldDispose && value != null) + value.Dispose(); + } + } + + /// <summary> + /// Disposes the underlying disposable as well as all future replacements. + /// </summary> + public void Dispose() + { + var old = default(IDisposable); + + lock (_gate) + { + if (!IsDisposed) + { + old = _current; + + // See IsDisposed for rationale behind using the sentinel. + _current = BooleanDisposable.True; + } + } + + if (old != null) + old.Dispose(); + } + } +} diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/RefCountDisposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/RefCountDisposable.cs new file mode 100644 index 0000000..9bd0407 --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/RefCountDisposable.cs @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Represents a disposable resource that only disposes its underlying disposable resource when all <see cref="GetDisposable">dependent disposable objects</see> have been disposed. + /// </summary> + public sealed class RefCountDisposable : ICancelable + { + private readonly object _gate = new object(); + private IDisposable _disposable; + private bool _isPrimaryDisposed; + private int _count; + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.RefCountDisposable"/> class with the specified disposable. + /// </summary> + /// <param name="disposable">Underlying disposable.</param> + /// <exception cref="ArgumentNullException"><paramref name="disposable"/> is null.</exception> + public RefCountDisposable(IDisposable disposable) + { + if (disposable == null) + throw new ArgumentNullException("disposable"); + + _disposable = disposable; + _isPrimaryDisposed = false; + _count = 0; + } + + /// <summary> + /// Gets a value that indicates whether the object is disposed. + /// </summary> + public bool IsDisposed + { + get { return _disposable == null; } + } + + /// <summary> + /// Returns a dependent disposable that when disposed decreases the refcount on the underlying disposable. + /// </summary> + /// <returns>A dependent disposable contributing to the reference count that manages the underlying disposable's lifetime.</returns> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Backward compat + non-trivial work for a property getter.")] + public IDisposable GetDisposable() + { + lock (_gate) + { + if (_disposable == null) + { + return Disposable.Empty; + } + else + { + _count++; + return new InnerDisposable(this); + } + } + } + + /// <summary> + /// Disposes the underlying disposable only when all dependent disposables have been disposed. + /// </summary> + public void Dispose() + { + var disposable = default(IDisposable); + lock (_gate) + { + if (_disposable != null) + { + if (!_isPrimaryDisposed) + { + _isPrimaryDisposed = true; + + if (_count == 0) + { + disposable = _disposable; + _disposable = null; + } + } + } + } + + if (disposable != null) + disposable.Dispose(); + } + + private void Release() + { + var disposable = default(IDisposable); + lock (_gate) + { + if (_disposable != null) + { + _count--; + + System.Diagnostics.Debug.Assert(_count >= 0); + + if (_isPrimaryDisposed) + { + if (_count == 0) + { + disposable = _disposable; + _disposable = null; + } + } + } + } + + if (disposable != null) + disposable.Dispose(); + } + + sealed class InnerDisposable : IDisposable + { + private RefCountDisposable _parent; + + public InnerDisposable(RefCountDisposable parent) + { + _parent = parent; + } + + public void Dispose() + { + var parent = Interlocked.Exchange(ref _parent, null); + if (parent != null) + parent.Release(); + } + } + } +} diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/ScheduledDisposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/ScheduledDisposable.cs new file mode 100644 index 0000000..eb742aa --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/ScheduledDisposable.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Reactive.Concurrency; +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Represents a disposable resource whose disposal invocation will be scheduled on the specified <seealso cref="T:System.Reactive.Concurrency.IScheduler"/>. + /// </summary> + public sealed class ScheduledDisposable : ICancelable + { + private readonly IScheduler _scheduler; + private volatile IDisposable _disposable; + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.ScheduledDisposable"/> class that uses an <see cref="T:System.Reactive.Concurrency.IScheduler"/> on which to dispose the disposable. + /// </summary> + /// <param name="scheduler">Scheduler where the disposable resource will be disposed on.</param> + /// <param name="disposable">Disposable resource to dispose on the given scheduler.</param> + /// <exception cref="ArgumentNullException"><paramref name="scheduler"/> or <paramref name="disposable"/> is null.</exception> + public ScheduledDisposable(IScheduler scheduler, IDisposable disposable) + { + if (scheduler == null) + throw new ArgumentNullException("scheduler"); + if (disposable == null) + throw new ArgumentNullException("disposable"); + + _scheduler = scheduler; + _disposable = disposable; + } + + /// <summary> + /// Gets the scheduler where the disposable resource will be disposed on. + /// </summary> + public IScheduler Scheduler + { + get { return _scheduler; } + } + + /// <summary> + /// Gets the underlying disposable. After disposal, the result is undefined. + /// </summary> + public IDisposable Disposable + { + get + { + var current = _disposable; + + if (current == BooleanDisposable.True) + return DefaultDisposable.Instance; // Don't leak the sentinel value. + + return current; + } + } + + /// <summary> + /// Gets a value that indicates whether the object is disposed. + /// </summary> + public bool IsDisposed + { + get { return _disposable == BooleanDisposable.True; } + } + + /// <summary> + /// Disposes the wrapped disposable on the provided scheduler. + /// </summary> + public void Dispose() + { + Scheduler.Schedule(DisposeInner); + } + + private void DisposeInner() + { +#pragma warning disable 0420 + var disposable = Interlocked.Exchange(ref _disposable, BooleanDisposable.True); +#pragma warning restore 0420 + + if (disposable != BooleanDisposable.True) + { + disposable.Dispose(); + } + } + } +} diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/SerialDisposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/SerialDisposable.cs new file mode 100644 index 0000000..835bd68 --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/SerialDisposable.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource, causing automatic disposal of the previous underlying disposable resource. + /// </summary> + public sealed class SerialDisposable : ICancelable + { + private readonly object _gate = new object(); + private IDisposable _current; + private bool _disposed; + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.SerialDisposable"/> class. + /// </summary> + public SerialDisposable() + { + } + + /// <summary> + /// Gets a value that indicates whether the object is disposed. + /// </summary> + public bool IsDisposed + { + get + { + lock (_gate) + { + return _disposed; + } + } + } + + /// <summary> + /// Gets or sets the underlying disposable. + /// </summary> + /// <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 + { + return _current; + } + + set + { + var shouldDispose = false; + var old = default(IDisposable); + lock (_gate) + { + shouldDispose = _disposed; + if (!shouldDispose) + { + old = _current; + _current = value; + } + } + if (old != null) + old.Dispose(); + if (shouldDispose && value != null) + value.Dispose(); + } + } + + /// <summary> + /// Disposes the underlying disposable as well as all future replacements. + /// </summary> + public void Dispose() + { + var old = default(IDisposable); + + lock (_gate) + { + if (!_disposed) + { + _disposed = true; + old = _current; + _current = null; + } + } + + if (old != null) + old.Dispose(); + } + } +} diff --git a/Rx.NET/System.Reactive.Core/Reactive/Disposables/SingleAssignmentDisposable.cs b/Rx.NET/System.Reactive.Core/Reactive/Disposables/SingleAssignmentDisposable.cs new file mode 100644 index 0000000..8931c25 --- /dev/null +++ b/Rx.NET/System.Reactive.Core/Reactive/Disposables/SingleAssignmentDisposable.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// <summary> + /// Represents a disposable resource which only allows a single assignment of its underlying disposable resource. + /// If an underlying disposable resource has already been set, future attempts to set the underlying disposable resource will throw an <see cref="T:System.InvalidOperationException"/>. + /// </summary> + public sealed class SingleAssignmentDisposable : ICancelable + { + private volatile IDisposable _current; + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Reactive.Disposables.SingleAssignmentDisposable"/> class. + /// </summary> + public SingleAssignmentDisposable() + { + } + + /// <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; + } + } + + /// <summary> + /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined. + /// </summary> + /// <exception cref="InvalidOperationException">Thrown if the 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 + { +#pragma warning disable 0420 + var old = Interlocked.CompareExchange(ref _current, value, null); +#pragma warning restore 0420 + if (old == null) + return; + + if (old != BooleanDisposable.True) + throw new InvalidOperationException(Strings_Core.DISPOSABLE_ALREADY_ASSIGNED); + + if (value != null) + value.Dispose(); + } + } + + /// <summary> + /// Disposes the underlying disposable. + /// </summary> + public void Dispose() + { +#pragma warning disable 0420 + var old = Interlocked.Exchange(ref _current, BooleanDisposable.True); +#pragma warning restore 0420 + if (old != null) + old.Dispose(); + } + } +} |