|
using System; |
|
using System.Threading; |
|
|
|
/// <summary> |
|
/// Provides an easy way handle situations where disposal is the current method's responsibility except on success. |
|
/// For example, throwing before returning a disposable object may otherwise result in the disposable object never |
|
/// being cleaned up. |
|
/// </summary> |
|
/// <example> |
|
/// <code> |
|
/// using var tempFile = OwnershipTracker.Create(new TempFile()); |
|
/// |
|
/// // Do stuff with the temporary file that involves reading from sources that may throw. |
|
/// // E.g.: |
|
/// using var file = File.Create(tempFile.OwnedInstance); |
|
/// await stream.CopyToAsync(file, cancellationToken).ConfigureAwait(false); |
|
/// |
|
/// return tempFile.ReleaseOwnership(); |
|
/// </code> |
|
/// </example> |
|
public static class OwnershipTracker |
|
{ |
|
/// <summary> |
|
/// Provides an easy way handle situations where disposal is the current method's responsibility except on success. |
|
/// For example, throwing before returning a disposable object may otherwise result in the disposable object never |
|
/// being cleaned up. |
|
/// </summary> |
|
/// <param name="instance">The instance which will be conditionally disposed by tracking its ownership.</param> |
|
/// <exception cref="ArgumentNullException">Thrown when the instance is <see langword="null"/>.</exception> |
|
public static OwnershipTracker<T> Create<T>(T instance) |
|
where T : class, IDisposable |
|
{ |
|
return new OwnershipTracker<T>(instance); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Provides an easy way handle situations where disposal is the current method's responsibility except on success. |
|
/// For example, throwing before returning a disposable object may otherwise result in the disposable object never |
|
/// being cleaned up. |
|
/// </summary> |
|
/// <example> |
|
/// <code> |
|
/// using var tempFile = OwnershipTracker.Create(new TempFile()); |
|
/// |
|
/// // Do stuff with the temporary file that involves reading from sources that may throw. |
|
/// // E.g.: |
|
/// using var file = File.Create(tempFile.OwnedInstance); |
|
/// await stream.CopyToAsync(file, cancellationToken).ConfigureAwait(false); |
|
/// |
|
/// return tempFile.ReleaseOwnership(); |
|
/// </code> |
|
/// </example> |
|
public sealed class OwnershipTracker<T> : IDisposable |
|
where T : class, IDisposable |
|
{ |
|
private T? instance; |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="OwnershipTracker{T}"/> class. Use <see |
|
/// cref="OwnershipTracker.Create{T}(T)"/> to enable generic argument inference. |
|
/// </summary> |
|
/// <param name="instance">The instance which will be conditionally disposed by tracking its ownership.</param> |
|
/// <exception cref="ArgumentNullException">Thrown when the instance is <see langword="null"/>.</exception> |
|
public OwnershipTracker(T instance) |
|
{ |
|
this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); |
|
} |
|
|
|
/// <summary> |
|
/// Disposes the instance if it is still owned. |
|
/// </summary> |
|
public void Dispose() |
|
{ |
|
Interlocked.Exchange(ref instance, null)?.Dispose(); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the instance if it is still owned, or throws <see cref="InvalidOperationException"/>. |
|
/// </summary> |
|
/// <exception cref="InvalidOperationException">Thrown when the instance is not owned.</exception> |
|
public T OwnedInstance => Volatile.Read(ref instance) ?? throw CreateException(); |
|
|
|
/// <summary> |
|
/// Stops tracking the instance as owned and returns the instance. Unless this method is called, <see |
|
/// cref="Dispose"/> will dispose the instance. |
|
/// </summary> |
|
/// <exception cref="InvalidOperationException">Thrown when the instance is not owned.</exception> |
|
public T ReleaseOwnership() |
|
{ |
|
return Interlocked.Exchange(ref instance, null) ?? throw CreateException(); |
|
} |
|
|
|
private static Exception CreateException() |
|
{ |
|
return new InvalidOperationException("The instance is no longer owned by this tracker."); |
|
} |
|
} |