-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9d42d1f
commit 9bcc26f
Showing
20 changed files
with
1,149 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
#if NETSTANDARD2_1 | ||
using System; | ||
|
||
namespace Nito.Disposables | ||
{ | ||
/// <summary> | ||
/// An instance that represents a reference count. All members are threadsafe. | ||
/// </summary> | ||
public interface IReferenceCountedAsyncDisposable<out T> : IAsyncDisposable | ||
where T : class, IAsyncDisposable | ||
{ | ||
/// <summary> | ||
/// Adds a weak reference to this reference counted disposable. Throws <see cref="ObjectDisposedException"/> if this instance is disposed. | ||
/// </summary> | ||
IWeakReferenceCountedAsyncDisposable<T> AddWeakReference(); | ||
|
||
/// <summary> | ||
/// Returns a new reference to this reference counted disposable, incrementing the reference counter. Throws <see cref="ObjectDisposedException"/> if this instance is disposed. | ||
/// </summary> | ||
IReferenceCountedAsyncDisposable<T> AddReference(); | ||
|
||
/// <summary> | ||
/// Gets the target object. Throws <see cref="ObjectDisposedException"/> if this instance is disposed. | ||
/// </summary> | ||
T? Target { get; } | ||
|
||
/// <summary> | ||
/// Whether this instance is currently disposing or has been disposed. | ||
/// </summary> | ||
public bool IsDisposeStarted { get; } | ||
|
||
/// <summary> | ||
/// Whether this instance is disposed (finished disposing). | ||
/// </summary> | ||
public bool IsDisposed { get; } | ||
|
||
/// <summary> | ||
/// Whether this instance is currently disposing, but not finished yet. | ||
/// </summary> | ||
public bool IsDisposing { get; } | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using System; | ||
|
||
namespace Nito.Disposables | ||
{ | ||
/// <summary> | ||
/// An instance that represents a reference count. All members are threadsafe. | ||
/// </summary> | ||
public interface IReferenceCountedDisposable<out T> : IDisposable | ||
where T : class, IDisposable | ||
{ | ||
/// <summary> | ||
/// Adds a weak reference to this reference counted disposable. Throws <see cref="ObjectDisposedException"/> if this instance is disposed. | ||
/// </summary> | ||
IWeakReferenceCountedDisposable<T> AddWeakReference(); | ||
|
||
/// <summary> | ||
/// Returns a new reference to this reference counted disposable, incrementing the reference counter. Throws <see cref="ObjectDisposedException"/> if this instance is disposed. | ||
/// </summary> | ||
IReferenceCountedDisposable<T> AddReference(); | ||
|
||
/// <summary> | ||
/// Gets the target object. Throws <see cref="ObjectDisposedException"/> if this instance is disposed. | ||
/// </summary> | ||
T? Target { get; } | ||
|
||
/// <summary> | ||
/// Whether this instance is currently disposing or has been disposed. | ||
/// </summary> | ||
public bool IsDisposeStarted { get; } | ||
|
||
/// <summary> | ||
/// Whether this instance is disposed (finished disposing). | ||
/// </summary> | ||
public bool IsDisposed { get; } | ||
|
||
/// <summary> | ||
/// Whether this instance is currently disposing, but not finished yet. | ||
/// </summary> | ||
public bool IsDisposing { get; } | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
src/Nito.Disposables/IWeakReferenceCountedAsyncDisposable.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#if NETSTANDARD2_1 | ||
using System; | ||
|
||
namespace Nito.Disposables | ||
{ | ||
/// <summary> | ||
/// An instance that represents an uncounted weak reference. All members are threadsafe. | ||
/// </summary> | ||
public interface IWeakReferenceCountedAsyncDisposable<out T> | ||
where T : class, IAsyncDisposable | ||
{ | ||
/// <summary> | ||
/// Adds a reference to this reference counted disposable. Returns <c>null</c> if the underlying disposable has already been disposed or garbage collected. | ||
/// </summary> | ||
IReferenceCountedAsyncDisposable<T>? TryAddReference(); | ||
|
||
/// <summary> | ||
/// Attempts to get the target object. Returns <c>null</c> if the underlying disposable has already been disposed or garbage collected. | ||
/// </summary> | ||
T? TryGetTarget(); | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using System; | ||
|
||
namespace Nito.Disposables | ||
{ | ||
/// <summary> | ||
/// An instance that represents an uncounted weak reference. All members are threadsafe. | ||
/// </summary> | ||
public interface IWeakReferenceCountedDisposable<out T> | ||
where T : class, IDisposable | ||
{ | ||
/// <summary> | ||
/// Adds a reference to this reference counted disposable. Returns <c>null</c> if the underlying disposable has already been disposed or garbage collected. | ||
/// </summary> | ||
IReferenceCountedDisposable<T>? TryAddReference(); | ||
|
||
/// <summary> | ||
/// Attempts to get the target object. Returns <c>null</c> if the underlying disposable has already been disposed or garbage collected. | ||
/// </summary> | ||
T? TryGetTarget(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System; | ||
|
||
namespace Nito.Disposables.Internals | ||
{ | ||
/// <summary> | ||
/// A reference count for an underlying target. | ||
/// </summary> | ||
public interface IReferenceCounter | ||
{ | ||
/// <summary> | ||
/// Increments the reference count and returns <c>true</c>. If the reference count has already reached zero, returns <c>false</c>. | ||
/// </summary> | ||
bool TryIncrementCount(); | ||
|
||
/// <summary> | ||
/// Decrements the reference count and returns <c>null</c>. If this call causes the reference count to reach zero, returns the underlying target. | ||
/// </summary> | ||
object? TryDecrementCount(); | ||
|
||
/// <summary> | ||
/// Returns the underlying target. Returns <c>null</c> if the reference count has already reached zero. | ||
/// </summary> | ||
object? TryGetTarget(); | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
src/Nito.Disposables/Internals/ReferenceCountedAsyncDisposable.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
#if NETSTANDARD2_1 | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Nito.Disposables.Internals | ||
{ | ||
/// <summary> | ||
/// An instance that represents a reference count. | ||
/// </summary> | ||
public sealed class ReferenceCountedAsyncDisposable<T> : SingleAsyncDisposable<IReferenceCounter>, IReferenceCountedAsyncDisposable<T> | ||
where T : class, IAsyncDisposable | ||
{ | ||
/// <summary> | ||
/// Initializes a reference counted disposable that refers to the specified reference count. The specified reference count must have already been incremented for this instance. | ||
/// </summary> | ||
public ReferenceCountedAsyncDisposable(IReferenceCounter referenceCounter) | ||
: base(referenceCounter) | ||
{ | ||
_ = referenceCounter ?? throw new ArgumentNullException(nameof(referenceCounter)); | ||
|
||
// Ensure we can cast from the stored IDisposable to T. | ||
_ = ((IReferenceCountedAsyncDisposable<T>) this).Target; | ||
} | ||
|
||
/// <inheritdoc/> | ||
protected override ValueTask DisposeAsync(IReferenceCounter referenceCounter) => (referenceCounter.TryDecrementCount() as IAsyncDisposable)?.DisposeAsync() ?? new ValueTask(); | ||
|
||
T? IReferenceCountedAsyncDisposable<T>.Target => (T?) ReferenceCounter.TryGetTarget(); | ||
|
||
IReferenceCountedAsyncDisposable<T> IReferenceCountedAsyncDisposable<T>.AddReference() | ||
{ | ||
var referenceCounter = ReferenceCounter; | ||
if (!referenceCounter.TryIncrementCount()) | ||
throw new ObjectDisposedException(nameof(ReferenceCountedAsyncDisposable<T>)); // cannot actually happen | ||
return new ReferenceCountedAsyncDisposable<T>(referenceCounter); | ||
} | ||
|
||
IWeakReferenceCountedAsyncDisposable<T> IReferenceCountedAsyncDisposable<T>.AddWeakReference() => new WeakReferenceCountedAsyncDisposable<T>(ReferenceCounter); | ||
|
||
private IReferenceCounter ReferenceCounter | ||
{ | ||
get | ||
{ | ||
IReferenceCounter referenceCounter = null!; | ||
// Implementation note: this always "succeeds" in updating the context since it always returns the same instance. | ||
// So, we know that this will be called at most once. It may also be called zero times if this instance is disposed. | ||
if (!TryUpdateContext(x => referenceCounter = x)) | ||
throw new ObjectDisposedException(nameof(ReferenceCountedAsyncDisposable<T>)); | ||
return referenceCounter; | ||
} | ||
} | ||
} | ||
} | ||
#endif |
53 changes: 53 additions & 0 deletions
53
src/Nito.Disposables/Internals/ReferenceCountedDisposable.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
|
||
namespace Nito.Disposables.Internals | ||
{ | ||
/// <summary> | ||
/// An instance that represents a reference count. | ||
/// </summary> | ||
public sealed class ReferenceCountedDisposable<T> : SingleDisposable<IReferenceCounter>, IReferenceCountedDisposable<T> | ||
where T : class, IDisposable | ||
{ | ||
/// <summary> | ||
/// Initializes a reference counted disposable that refers to the specified reference count. The specified reference count must have already been incremented for this instance. | ||
/// </summary> | ||
public ReferenceCountedDisposable(IReferenceCounter referenceCounter) | ||
: base(referenceCounter) | ||
{ | ||
_ = referenceCounter ?? throw new ArgumentNullException(nameof(referenceCounter)); | ||
|
||
// Ensure we can cast from the stored IDisposable to T. | ||
_ = ((IReferenceCountedDisposable<T>) this).Target; | ||
} | ||
|
||
/// <inheritdoc/> | ||
protected override void Dispose(IReferenceCounter referenceCounter) => (referenceCounter.TryDecrementCount() as IDisposable)?.Dispose(); | ||
|
||
T? IReferenceCountedDisposable<T>.Target => (T?) ReferenceCounter.TryGetTarget(); | ||
|
||
IReferenceCountedDisposable<T> IReferenceCountedDisposable<T>.AddReference() | ||
{ | ||
var referenceCounter = ReferenceCounter; | ||
if (!referenceCounter.TryIncrementCount()) | ||
throw new ObjectDisposedException(nameof(ReferenceCountedDisposable<T>)); // cannot actually happen | ||
return new ReferenceCountedDisposable<T>(referenceCounter); | ||
} | ||
|
||
IWeakReferenceCountedDisposable<T> IReferenceCountedDisposable<T>.AddWeakReference() => new WeakReferenceCountedDisposable<T>(ReferenceCounter); | ||
|
||
private IReferenceCounter ReferenceCounter | ||
{ | ||
get | ||
{ | ||
IReferenceCounter referenceCounter = null!; | ||
// Implementation note: this always "succeeds" in updating the context since it always returns the same instance. | ||
// So, we know that this will be called at most once. It may also be called zero times if this instance is disposed. | ||
if (!TryUpdateContext(x => referenceCounter = x)) | ||
throw new ObjectDisposedException(nameof(ReferenceCountedDisposable<T>)); | ||
return referenceCounter; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
using System; | ||
using System.Threading; | ||
|
||
namespace Nito.Disposables.Internals | ||
{ | ||
/// <summary> | ||
/// A reference count for an underlying target. | ||
/// </summary> | ||
public sealed class ReferenceCounter : IReferenceCounter | ||
{ | ||
private object? _target; | ||
private int _count; | ||
|
||
/// <summary> | ||
/// Creates a new reference counter with a reference count of 1 referencing the specified target. | ||
/// </summary> | ||
public ReferenceCounter(object? target) | ||
{ | ||
_target = target; | ||
_count = 1; | ||
} | ||
|
||
bool IReferenceCounter.TryIncrementCount() => TryUpdate(x => x + 1) != null; | ||
|
||
object? IReferenceCounter.TryDecrementCount() | ||
{ | ||
var updateResult = TryUpdate(x => x - 1); | ||
if (updateResult != 0) | ||
return null; | ||
return Interlocked.Exchange(ref _target, null); | ||
} | ||
|
||
object? IReferenceCounter.TryGetTarget() | ||
{ | ||
var result = Interlocked.CompareExchange(ref _target, null, null); | ||
var count = Interlocked.CompareExchange(ref _count, 0, 0); | ||
if (count == 0) | ||
return null; | ||
return result; | ||
} | ||
|
||
private int? TryUpdate(Func<int, int> func) | ||
{ | ||
while (true) | ||
{ | ||
var original = Interlocked.CompareExchange(ref _count, 0, 0); | ||
if (original == 0) | ||
return null; | ||
var updatedCount = func(original); | ||
var result = Interlocked.CompareExchange(ref _count, updatedCount, original); | ||
if (original == result) | ||
return updatedCount; | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.