It's a little difficult to give a full review without the removed null checks, but I'll give an observation:
In Dispose()
, you depend on null
to be the value of _state
to judge whether or not to call the cleanup action. That's all well and good, but you should likely then constrain the generic parameter T
to be class
class Usingifier<T> : IDisposable where T : class
You also have an issue with multiple-disposals calling the cleanup action with the state - both probably being stale after the fact. The class should be considered unusable after the cleanup. Or at least unusable until the initialize function has been called again. Simple boolean should take care of that:
class Usingifier<T> : IDisposable where T : class
{
private readonly Func<T> _initialize;
private readonly Action<T> _cleanUp;
private bool _disposed;
private T _state;
public Usingifier(Func<T> initialize, Action<T> cleanUp)
{
_initialize = initialize;
_cleanUp = cleanUp;
}
public Usingifier<T> Initialize()
{
_state = _initialize();
_disposed = false;
return this;
}
public virtual void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
if (_state != null)
{
_cleanUp(_state);
}
}
}
You may want to also put in some sort of nice (optional) automatic disposal if T
itself happens to be IDisposable
. Here's a quick take on that:
class Usingifier<T> : IDisposable where T : class
{
private readonly Func<T> _initialize;
private readonly Action<T> _cleanUp;
private readonly bool _autoDispose;
private bool _disposed;
private T _state;
public Usingifier(Func<T> initialize, Action<T> cleanUp, bool autoDispose = false)
{
_initialize = initialize;
_cleanUp = cleanUp;
_autoDispose = autoDispose;
}
public Usingifier<T> Initialize()
{
_state = _initialize();
_disposed = false;
return this;
}
public virtual void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
if (_state != null)
{
_cleanUp(_state);
}
if (_autoDispose && _state is IDisposable disposable)
{
disposable.Dispose();
}
}
I had a couple more thoughts: perhaps expose _state
via a property (can then be accessed within your using
scope) and then perhaps extracting an interface for the whole caboodle for mocking purposes during unit testing. Behold:
interface IUsingifier<T> : IDisposable where T : class
{
T State { get; }
Usingifier<T> Initialize();
}
class Usingifier<T> : IUsingifier<T> where T : class
{
private readonly Func<T> _initialize;
private readonly Action<T> _cleanUp;
private readonly bool _autoDispose;
private bool _disposed;
public Usingifier(Func<T> initialize, Action<T> cleanUp, bool autoDispose = false)
{
_initialize = initialize;
_cleanUp = cleanUp;
_autoDispose = autoDispose;
}
public T State { get; private set; }
public Usingifier<T> Initialize()
{
State = _initialize();
_disposed = false;
return this;
}
public virtual void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
if (State != null)
{
_cleanUp(State);
}
if (_autoDispose && State is IDisposable disposable)
{
disposable.Dispose();
}
}
}