Problem
I’ve made this extension method. Its purpose is to trigger the disposal of a value, stored in a Nito.AsyncEx AsyncLazy
, as authored by @StephenCleary.
Is this an approach that makes sense? Is the code good?
using System;
using System.Threading.Tasks;
using Nito.AsyncEx;
/// <summary>
/// Extensions to types in the <see cref="Nito.AsyncEx"> namespace.
/// </summary>
public static class NitoAsyncEx
{
/// <summary>
/// Disposes the specified <see cref="AsyncLazy"> value.
/// </summary>
/// <typeparam name="T">The type of the lazy value.</typeparam>
/// <param name="asyncLazy> The asynchronous lazy.</param>
/// <returns>When disposal is complete.</returns>
public static async Task Dispose<T>(
this AsyncLazy<T> asyncLazy) where T : IDisposable
{
if (asyncLazy.Id != 0)
{
(await asyncLazy).Dispose();
}
}
}
Solution
It’s potentially dangerous to only conditionally dispose of the resource. If you don’t dispose it if the resource was never initialized in the first place then you run the risk of some code actually initializing it later, and then not disposing of that resource.
The safer design would be to wrap the object, rather than just having this one extension method.
public class DisposableAsyncLazy<T> : IDisposable
where T : IDisposable
{
private AsyncLazy<T> lazy;
private bool disposed = false;
public DisposableAsyncLazy(Func<T> factory)
{
lazy = new AsyncLazy<T>(factory);
}
public DisposableAsyncLazy(Func<Task<T>> factory)
{
lazy = new AsyncLazy<T>(factory);
}
public int Id
{
get
{
return lazy.Id;
}
}
public async Task<T> GetValue()
{
if (!disposed)
return await lazy;
else
throw new ObjectDisposedException("DisposableAsyncLazy");
}
public void Start()
{
if (!disposed)
lazy.Start();
else
throw new ObjectDisposedException("DisposableAsyncLazy");
}
public async void Dispose()
{
disposed = true;
if (lazy.Id != 0)
(await lazy).Dispose();
}
}
(Note you can make DisposableAsyncLazy
awaitable, rather than having a method/property produce a task, if you want to take the time to do so.)