In UMLs composition aggregations I tend to use the IDisposable
interface/pattern to force the "Child
can't exist without Parent
" - requirement the composition demands:
public class Parent : IDisposable
{
private readonly _childs;
public IReadOnlyCollection<Child> Childs => _childs;
public Parent()
{
_childs = new List<Child>();
}
public Child CreateChild()
{
var child = new Child();
_childs.Add(child);
return child;
}
public void Dispose()
{
foreach (var child in _childs)
child.Dispose();
}
}
public class Child : IDisposable
{
internal Child() {}
public void Dispose()
{
//Cleanup object, imagine an idempotent implementation
}
}
So far, so good. But now imagine this piece of code:
var parent = new Parent();
var child = parent.CreateChild();
child.Dispose();
//At this point parent.Childs contains a disposed child object
Since I am currently facing such a situation in a library I develop, several questions come to my mind:
- Is it okay, that
parent.Childs
contains an (in practise) unusable object?- If yes
- would you ignore it, since it's the users own decision to prematurely dispose it?
- If not
- Is there some kind of best-practise on how to deal with premature disposal of child-objects in C#? My first thought was the use of a callback function/delegate on disposal of the child object to remove itself from the list of active instances, but to me that sounds rather clumsy.
- Is there another excuse so that I can wash my hands of responsibility?
- If yes
From an architectural point of view the main problem is, that IDisposable
is visible to everyone being able to obtain an instance of Child
. Hiding it basically means making use of OO-polymorphy and extract the ability of disposal to an invisible implementation. But for many classes in a domain model this gets an absolutely bloating factor with no additional benefit. Furthermore it inherently interprets UMLs composition aggregation as "Child
can only be destroyed by Parent
", which is incorrect in my opinion:
public interface IChild
{
//Child methods
}
internal class Child : IChild, IDisposable
{
//See implementation above
}
public class Parent : IDisposable
{
public IReadOnlyCollection<IChild> Childs => _childs;
public IChild CreateChild()
{
var child = new Child();
_childs.Add(child);
return child;
}
}