Constructor order in subclasses

1.5k views Asked by At

In a descendant class, is there a way to call both the public, parameterized constructor, as well as the protected/private constructor, while still making a call to the base class' constructor?

For example, given the following code:

using System;

public class Test
{
  void Main()
  {
    var b = new B(1);
  }

  class A
  {
    protected A()
    {
      Console.WriteLine("A()");
    }


    public A(int val)
      : this()
    {
      Console.WriteLine("A({0})", val);
    }

  }

  class B : A
  {
    protected B()
    {
      Console.WriteLine("B()");
    }

    public B(int val)
      : base(val)
    {
      Console.WriteLine("B({0})", val);
    }

  }
}

the output given is:

A()
A(1)
B(1)

However, this is what I was expecting:

A()
B()
A(1)
B(1)

Is there a way to achieve this through constructor chaining? Or should I have an OnInitialize() type method in A that is either abstract or virtual which is overridden in B and called from the protected, parameter-less constructor of A?

2

There are 2 answers

0
Adam Robinson On

No, what you're looking for is not possible using only constructors. You're essentially asking the constructor chain to "branch", which is not possible; each constructor may call one (and only one) constructor either in the current class or the parent class.

The only way to accomplish this is (as you suggest) through the use of a virtual initialization function that you invoke in the base class. Ordinarily, calling virtual members from within a constructor is frowned upon, as you can cause code in a child class to execute before its constructor, but given that that's exactly what you're after, that's your only real approach here.

2
Jon Hanna On

Your comment has two misconceptions in it.

First, constructors in derived classes do not override constructors in base classes. Rather, they call them. Overriding a method means it's called instead of a base classes equivalent method, not as well as.

Secondly, there is always a base constructor called. Any constructor without an explicit call to a base constructor implicitly calls the parameterless base constructor.

You can demonstrate this by deleting the A() constructor, or making it private. The B() constructor that doesn't call a base constructor will now be a compiler error, because there's no parameterless constructor available for the implicit call.

Because B is derived from A, B is an A. Therefore B can't be successfully constructed without A being constructed (like building a sports car without building a car).

Construction happens base-first. It is the responsibility of A's constructors to make sure that the instance of A is fully constructed and in a valid state. This is then the starting point for B's constructor to put the instance of B into a valid state.

As for the idea of:

Or should I have an OnInitialize() type method in A that is either abstract or virtual which is overridden in B and called from the protected, parameter-less constructor of A?

Definitely not. At the point where A's constructor is called, the rest of B has yet to be constructed. However, initialisers have run. This leads to really nasty situations like:

public abstract class A
{
  private int _theVitalValue;
  public A()
  {
    _theVitalValue = TheValueDecider();
  }
  protected abstract int TheValueDecider();
  public int TheImportantValue
  {
    get { return _theVitalValue; }
  }
}
public class B : A
{
  private readonly int _theValueMemoiser;
  public B(int val)
  {
    _theValueMemoiser = val;
  }
  protected override int TheValueDecider()
  {
    return _theValueMemoiser;
  }
}
void Main()
{
  B b = new B(93);
  Console.WriteLine(b.TheImportantValue); // Writes "0"!
}

At the time when A calls the virtual method overridden in B, B's initialisers have run, but not the rest of its constructor. The virtual method is therefore being run on a B that is not in a valid sate.

It's certainly possible to make such approaches work, but its also really easy to have it cause lots of pain.

Instead:

Define your classes so that they each always leave their constructors in a completely initialised and valid state. It is at this point that you set up the invariant for that class (an invariant is the set of conditions that must hold true at all times a class is observed from the outside. E.g. it's okay for a date class that internally held month and day values in separate fields to temporarily be in a condition of February the 31st, but it must never be in such a state at the end of construction, or at the beginning or end of a method, because calling code will expect it to be a date).

It is okay for an abstract class to depend upon derived class to fulfil its rĂ´le, but not for its initial state. If a design is moving towards this becoming necessary, then move the responsibility for that state further down the heirarcy. For example, consider:

public abstract class User
{
  private bool _hasFullAccess;
  protected User()
  {
    _hasFullAccess = CanSeeOtherUsersItems && CanEdit;
  }
  public bool HasFullAccess
  {
    get { return _hasFullAccess; }
  }
  protected abstract bool CanSeeOtherUsersItems {get;}
  protected abstract bool CanEdit {get;}
}
public class Admin : User
{
  protected override bool CanSeeOtherUsersItems
  {
    get { return true; }
  }
  protected override bool CanEdit
  {
    get { return true; }
  }
}
public class Auditor : User
{
  protected override bool CanSeeOtherUsersItems
  {
    get { return true; }
  }
  protected override bool CanEdit
  {
    get { return false; }
  }
}

Here we're depending upon information in the derived classes to set the state of the base class, and upon that state to fulfil a task. This will work here, but won't if the properties depend upon state in the derived classes (which it would be perfectly reasonable for someone creating a new derived class to assume was allowed). Instead, we make the functionality but not the state depend on the derived classes by changing User to:

public abstract class User
{
  public bool HasFullAccess
  {
    get { return CanSeeOtherUsersItems && CanEdit; }
  }
  protected abstract bool CanSeeOtherUsersItems {get;}
  protected abstract bool CanEdit {get;}
}

(If we expected such calls to be expensive sometimes, we can still memoise the result upon first call, because that can only happen in a fully constructed User, and the states both prior to and after the memoisation are valid and accurate states for User to be in).

Having defined a base class to leave its constructor in a valid state, we then do the same for the derived class. It can depend on the state of its base class in its constructor, because its base class is fully constructed and that is step 1 of it being constructed. It can't depend on this in its initialisers, though that really doesn't come up any where near as much as something people want to try.

To return to the expected output of:

A()
B()
A(1)
B(1)

This means you wanted to put something into a valid starting state and then into a different valid starting state. That doesn't make any sense.

Now, of course we can use the this() form to call a constructor from a constructor, but this should be seen purely as a convenience to save typing. It means "this constructor does everything the one called does, and then some" and nothing more. To the outside world, only one constructor was ever called. In languages without this convenience we would either duplicate code or do:

private void repeatedSetupCode(int someVal, string someOtherVal)
{
  someMember = someVal;
  someOtherMember = someOtherVal;
}
public A(int someVal, string someOtherVal)
{
  repeatedSetupCode(someVal, someOtherVal);
}
public A(int someVal)
{
  repeatedSetupCode(someVal, null);
}
public A()
{
  repeatedSetupCode(0, null);
}

It's nice that we have this(). It doesn't save much typing, but it does make it clear that the stuff in question belongs to the construction phase of object lifetime alone. But if we really need to, we can use the form above, and even make repeatedSetupCode protected.

I would be very cautious though. Objects dealing with each other's construction in any manner other than calling the single base constructor necessary to put that base class into a valid state according to the invariant of that base class, is a bad smell that suggests a different approach entirely might be better.