Home > Net >  How do I make sure all these constructors are called?
How do I make sure all these constructors are called?

Time:03-19

This is about c# constructors and has been asked before a couple of times. However, the questions and answers never really fit my scenario:

public abstract class BaseClass {

    public List<int> MyIntegers { get; set; }

    public BaseClass()
    {
        this.MyIntegers = new List<int>();
    }
    public BaseClass(int initialInteger) : this()
    {
        this.MyIntegers.Add(initialInteger);
    }
}

public class DerivedClass : BaseClass
{
    public List<string> MyStrings { get; set; }

    public DerivedClass()
    {
        this.MyStrings = new List<string>();    // does never get initialized
    }
    public DerivedClass(int initialInteger) : base(initialInteger)
    {
        this.MyStrings.Add(initialInteger.ToString()); <-- exception because DerivedClass() is never called
    }
}

// In Program.cs (or where ever)
var derivedClass = new DerivedClass(10); 

The code above will fail because the parameterless constructor of DerivedClass is never called. So, List<string> MyStrings is not initialized.

I know I can't do constructor chaining with base() and this() at the same time. But of course I could do this:

// Workaround      
public DerivedClass(int initialInteger) : base(initialInteger)
{
   this.MyStrings = new List<string>(); // redundant
   this.MyStrings.Add(initialInteger.ToString());
}

This works, but it's pretty ugly because I am using redundant code. It seems ok here in the simple sample, but in my real application I would have to add a lot of redundant code. Not nice.

How would you solve this elegantly?

CodePudding user response:

As a rule of thumb, a constructor with few parameter should delegate to one with more parameters, ideally having one single constructor that does all the actual initialization.

public abstract class BaseClass {

    public List<int> MyIntegers { get; set; }

    public BaseClass() : this(new List<int>())    {    }
    public BaseClass(int initialInteger) : this(new List<int>(){initialInteger}) {   }
    protected BaseClass(List<int> myIntegers) => MyIntegers = myIntegers;
}

public class DerivedClass : BaseClass
{
    public List<string> MyStrings { get; set; }
    public DerivedClass () : this(new List<string>(), new List<int>())    {    }
    public DerivedClass (int initialInteger) : this(new List<string>(){initialInteger},new List<int>(){initialInteger}) {   }
    protected DerivedClass (List<string> myStrings, List<int> myIntegers) : base(myIntegers) => MyStrings = myStrings;
}

The downside with this is that some initialization-logic is duplicated, i.e. the derived class needs to create a list of integers to give to the base-class. If your creation logic is to complicated it might hint that your model is problematic, or that you should extract some of the creation logic to a separate factory class. Another tool that might be useful is an inversion of control container.

This assumes the given problem is a placeholder for something else, since this specific problem could easily be solved by just taking a params int[] initialValues as Damien_The_Unbeliever suggest in the comments.

  • Related