C# – the best way to initialize a child’s reference to its parent

cpatterns-and-practices

I'm developing an object model that has lots of different parent/child classes. Each child object has a reference to its parent object. I can think of (and have tried) several ways to initialize the parent reference, but I find significant drawbacks to each approach. Given the approaches described below which is best … or what is even better.

I'm not going to make sure the code below compiles so please try to see my intention if the code is not syntactically correct.

Note that some of my child class constructors do take parameters (other than parent) even though I don't always show any.

  1. Caller is responsible for setting parent and adding to the same parent.

    class Child {
      public Child(Parent parent) {Parent=parent;}
      public Parent Parent {get; private set;}
    }
    class Parent {
      // singleton child
      public Child Child {get; set;}
      //children
      private List<Child> _children = new List<Child>();
      public List<Child> Children { get {return _children;} }
    }
    

    Downside: setting parent is a two-step process for the consumer.

    var child = new Child(parent);
    parent.Children.Add(child);
    

    Downside: error prone. Caller can add child to a different parent than was used to initialize the child.

    var child = new Child(parent1);
    parent2.Children.Add(child);
    
  2. Parent verifies that caller adds child to parent for which was initialized.

    class Child {
      public Child(Parent parent) {Parent = parent;}
      public Parent Parent {get; private set;}
    }
    class Parent {
      // singleton child
      private Child _child;
      public Child Child {
        get {return _child;}
        set {
          if (value.Parent != this) throw new Exception();
          _child=value;
        }
      }
      //children
      private List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
      public void AddChild(Child child) {
        if (child.Parent != this) throw new Exception();
        _children.Add(child);
      }
    }
    

    Downside: Caller still has a two-step process for setting parent.

    Downside: runtime checking – reduces performance and adds code to every add/setter.

  3. The parent sets the child's parent reference (to itself) when the child is added/assigned to a parent. Parent setter is internal.

    class Child {
      public Parent Parent {get; internal set;}
    }
    class Parent {
      // singleton child
      private Child _child;
      public Child Child {
        get {return _child;}
        set {
          value.Parent = this;
          _child = value;
        }
      }
      //children
      private List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
      public void AddChild(Child child) {
        child.Parent = this;
        _children.Add(child);
      }
    }
    

    Downside: The child is created without a parent reference. Sometimes initialization/validation requires the parent which means some initialization/validation must be performed in the child’s parent setter. The code can get complicated. It would be so much easier to implement the child if it always had its parent reference.

  4. Parent exposes factory add methods so that a child always has a parent reference. Child ctor is internal. Parent setter is private.

    class Child {
      internal Child(Parent parent, init-params) {Parent = parent;}
      public Parent Parent {get; private set;}
    }
    class Parent {
      // singleton child
      public Child Child {get; private set;}
      public void CreateChild(init-params) {
          var child = new Child(this, init-params);
          Child = value;
      }
      //children
      private List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
      public Child AddChild(init-params) {
        var child = new Child(this, init-params);
        _children.Add(child);
        return child;
      }
    }
    

    Downside: Can’t use initialization syntax such as new Child(){prop = value}.
    Instead have to do:

    var c = parent.AddChild(); 
    c.prop = value;
    

    Downside: Have to duplicate the parameters of the child constructor in the add-factory methods.

    Downside: Can’t use a property setter for a singleton child. It seems lame that I need a method to set the value but provide read access via a property getter. It’s lopsided.

  5. Child adds itself to the parent referenced in its constructor. Child ctor is public. No public add access from parent.

    //singleton
    class Child{
      public Child(ParentWithChild parent) {
        Parent = parent;
        Parent.Child = this;
      }
      public ParentWithChild Parent {get; private set;}
    }
    class ParentWithChild {
      public Child Child {get; internal set;}
    }
    
    //children
    class Child {
      public Child(ParentWithChildren parent) {
        Parent = parent;
        Parent._children.Add(this);
      }
      public ParentWithChildren Parent {get; private set;}
    }
    class ParentWithChildren {
      internal List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
    }
    

    Downside: calling syntax is not great. Normally one calls an add method on the parent instead of just creating an object like this:

    var parent = new ParentWithChildren();
    new Child(parent); //adds child to parent
    new Child(parent);
    new Child(parent);
    

    And sets a property rather than just creating an object like this:

    var parent = new ParentWithChild();
    new Child(parent); // sets parent.Child
    

I just learned that SE does not allow some subjective questions and clearly this is a subjective question. But, maybe it is a good subjective question.

Best Answer

I would stay away from any scenario which necessarily requires the child to know about the parent.

There are ways of passing messages from child to parent through events. This way the parent, upon add, simply has to register to an event that the child triggers, without the child having to know about the parent directly. Afterall, this is likely the intended usage of a child knowing about its parent, in order to be able to use the parent to some effect. Except you wouldn't want the child performing the job of the parent, so really what you want to do is simply tell the parent that something has happened. Therefore, what you need to handle is an event on the child, of which the parent can take advantage.

This pattern also scales very well should this event become useful to other classes. Perhaps it is a bit of an overkill, but it also prevents you from shooting yourself in the foot later, since it becomes tempting to want to use parent in your child class which only couples the two classes even more. Refactoring of such classes afterwards is time-consuming and can easily create bugs in your program.

Hope that helps!