A note on constructor chaining

Friday, 5 May 2006 11:56 by RanjanBanerji
(Create a "full" value constructor)

In his book Effective C# Bill Wagner talks about Constructor Chaining as a good practice.  As I read through the chapter I found that something was bothering me.  No about constructor chaining in itself but something else.  Then it finally came to me.  I realized that implementing constructor chaining can become quite cumbersome when refactoring code.  Let me explain.

Lets suppose we write the following code:

 

class Test {
     protected int x1;
     protected int x2;

     public Test() : this( 0, 0 ) {
     }

     public Test( int px1, int px2 ) {
         x1 = px1;
         x2 = px2;
     }

     public int X1 {
         get;
         set; //code omitted to save space :-)
     }

     public int X2 {
         get;
         set; //code omitted to save space :-)
     }
 }

 

What we have above is a good example of constructor chaining.  In my experience, however, I have observed that developers do not write code in this manner.  At last not in the C# world.  When I did C++ programming I always created:

  1. A default constructor.

  2. A copy constructor.

  3. An operator =. and

  4. At least one value constructor that would take all parameters required to initialize all public properties (get, set methods).

I believe all or at least the first three of the above came from Effective C++ by Scott Meyers. Jumping back to the C# world the code style I tend to see more often is as follows:

class Test {
    protected int x1;
    protected int x2;

    public Test() {
    }
    
    public int X1 {
        get; set; //I am too lazy to fill this out :-)
    }

    public int X2 {
        get; set; //I am too lazy to fill this out :-)
    }
}    

//Then somewhere in the code you will see

public void SomeMethod() {
    Test test = new Test();
    test.X1 = 45;
    test.X2 = 3;
}

 

This may seem ok at first but soon can become a nightmare as the number of data members in your class increases.  While preliminary design should warn you about such changes, in a lot of cases you just do not know what your class may finally look like (This is not a post about process and I am sure we have all gone through those crisis moments when requirements come in the final moments).  What this means is that your simple class with one default constructor over time may end up becoming a large class with lots of constructors.

Each time you add a new constructor you may find yourself refactoring a lot of constructors and find that constructor chaining is becoming a cumbersome process.

Alternatively, you can follow the practice of creating:

  • A default constructor and

  • A "full" value constructor that will provide an argument for each public property your class will expose.

  • Always chain the default to the "full" value constructor.

  • Each time you add new data to your class that will have a public property, modify your "full" value constructor and the chaining to it from your default constructor.

The advantages to this approach are:

  1. You can create instances of your class more efficiently using the "full" value constructor rather than creating an instance using the default and then assigning values via properties.

  2. Calling the default constructor will efficiently initialize data via chaining.

  3. If you add an "intermediate" constructor (one with more than zero arguments but less than the number of arguments in the "full" constructor), you know exactly what to chain it to for initialization of the remaining data.

//Creating a class like this makes it easier to refactor later
class Test {
    protected int x1;
    protected int x2;
    protected int x3;
    protected int x4;
    protected int x5;

    //Always chain default to full value constructor
    public Test() : this( 0, 0, 0, 0, 0 ) {
    }
    
    public Test( int px1, int px2 ,int px3, int px4, int px5 ) {
        //assignments
        x1 = px1;
        .
        .
        .
    }

    public int X1 {
        get; set; //I am too lazy to fill this out :-)
    }

    public int X2 {
        get; set; //I am too lazy to fill this out :-)
    }
    .
    .
    .

}

Now adding a new "intermediate" constructor is easy. Just add and chain to the "full" constructor

//Creating a class like this makes it easier to refactor later
class Test {
    protected int x1;
    protected int x2;
    protected int x3;
    protected int x4;
    protected int x5;

    //Always chain default to full value constructor
    public Test() : this( 0, 0, 0, 0, 0 ) {
    }
    
    //Constructor Added later but has the "full" constructor available to chain to
    public Test( int px1, int px2 ) : this( px1, px2, 0, 0, 0 ) {
    }

    public Test( int px1, int px2 ,int px3, int px4, int px5 ) {
        //assignments
        x1 = px1;
        .
        .
    }

    public int X1 {
        get; set; //I am too lazy to fill this out :-)
    }

    public int X2 {
        get; set; //I am too lazy to fill this out :-)
    }
    .
    .
    .

}

 

Though nothing ground breaking in this approach, I think it will make certain coding practices a lot easier.

Tags:  
Categories:   .Net
Actions:   E-mail | Permalink | Comments (4) | Comment RSSRSS comment feed