Update at the end
Is it possible to implement IDisposable
pattern correctly while using object composition principle to promote code-reuse, reduce code duplication and hide verbose "official" implementation?
Rational
- Composition over inheritance is a good principle
- Implementing
IDisposable
correctly and thoroughly is very verbose and cumbersome
Proposal
Delegate the dispose logic to a dedicated class:
public class DisposeManager
{
public Action Managed { get; set; }
public Action Unmanaged { get; set; }
protected virtual void Dispose(bool disposing)
{
// only dispose once
if (disposed)
return;
if (disposing)
{
Managed?.Invoke();
}
Unmanaged?.Invoke();
disposed = true;
}
public void DisposeObject(object o)
{
Dispose(disposing: true);
GC.SuppressFinalize(o);
}
public void FinalizeObject()
{
Dispose(disposing: false);
}
private bool disposed;
}
Implement IDisposable
in user class in the following way:
public class DisposeUser : IDisposable
{
public DisposeUser()
{
// using lambda
disposeManager.Managed = () =>
{
// [...]
};
// or using member method
disposeManager.Unmanaged = DisposeUnmanaged;
}
~DisposeUser()
{
disposeManager.FinalizeObject();
}
public void Dispose()
{
disposeManager.DisposeObject(this);
}
private void DisposeUnmanaged()
{
// [...]
}
private readonly DisposeManager disposeManager = new DisposeManager();
}
Benefits
- much simpler to implement for user classes
- more explicit (managed, unmanaged)
- use composition
- remove the needs for multiple base classes all implementing the dispose pattern and creating code duplication
Questions
- Is it ever a good idea or more of a programmer fancy "improvement" idea ?
- I've made a decent number of research on the dispose pattern and implementation but never found someone suggesting such idea, any reason why?
- Any potential problems around hard refs, especially with Action capturing members, etc. that would prevent the actual user class to be collected correctly?
- Other thought?
Thanks!
Update
Drawbacks mention in answers
Mainly the drawbacks that were pointed in the answers are:
- probably overkill for most case because it's rare to have unmanaged resources to dispose
- doesn't cover
IAsyncDisposable
- performance impact (delegates)
Managed
andUnmanaged
action properties can re-set later- Loose the well-known pattern knowledge for other developers
- Not much simpler/reducing code
New version
After using this base version in a project, I've made some improvements that may address some of the drawbacks mentioned. However this new version also accentuates some of them.
Changes
- expose list of actions to allow child class to easily add dispose logic
- add extensions methods to ease the add of disposable child(s)
- extension method to register to an event and unregister on dispose in one line
Those changes doesn't address much of the drawbacks mentioned, but I think it starts to bring enough benefits for the developer on a daily-basis usage to be worth it. Of course it's the case only if performance is not a concern and you need to dispose stuff in various places.
but never found someone suggesting such idea, any reason why?
Disposing/Memory Management is a pretty sensitive to implementation topic. The solution for 99% cases looks like overkill (i have no unmanaged resources to dispose), for 0,99% looks like too generic because a very special implementation is needed. Also performance concern forces me to think about delegate allocations which can be simply avoided with implementingIDisposable
natively. Finally, why there's nothing aboutIAsyncDisposable
? \$\endgroup\$DisposeManager
'sManaged
andUnmanaged
properties feel so wrong. Allowing consumers to define their own method without any constraint can be really dangerous. For example they can call ReRegisterForFinalize and with that they can resurrect the object. \$\endgroup\$IDisposable
directly were needed and ignore unmanaged. However, what if a derived class needs to dispose unmanaged resources but the base class already implementIDisposable
with a simplepublic virtual void Dispose()
that doesn't deal with unmanaged? \$\endgroup\$