Skip to content

Instantly share code, notes, and snippets.

@jnm2
Last active December 9, 2019 19:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jnm2/2302a81f7b8797d9e59a84c69d1e12bf to your computer and use it in GitHub Desktop.
Save jnm2/2302a81f7b8797d9e59a84c69d1e12bf to your computer and use it in GitHub Desktop.

Nice for making sure stuff is cleaned up for methods that can throw but that give ownership to the caller on success rather than disposing:

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();
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.");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment