Constructors often seem to me like Leap Year Logic (another post I should do): something that people know conversationally but are surprised by when they try to implement. Its come up enough times in recent weeks that a quick constructors primer is in order, if for nothing other than a reference post.
So, lets look at some constructor basics.
Constructors are methods/functions
I suppose one could look at constructors this way. So much of programming, so much of life in general, is finding a way to link a new factoid back to an existing piece of knowledge, thereby reducing the learning burden. The problem with that is by reducing the learning burden, you have reduced, well, the learning. Or you could say you have reduced the understanding, which was your intent in the first place (the understanding, not the reduction). So while a constructor looks and behaves similiarly to a method/function, its incomplete to leave it there.
Constructors are a special block of statements
Ok, this tells us less but is more correct. So what makes this "special", and why "block of statements" instead of "method/function"? Part of the specialness of constructors is that they are invoked when an object is instantiated. That is to say when you type:
DerivedClass dc = new DerivedClass();
Everything after the 'new' keyword is the constructor. In fact, the 'new' keyword is a part of it as well, acting as a trigger to the compiler that object creation is occuring and that the next statement is a constructor.
Like a method/function, constuctors can be overloaded. So if our class defined it, we could have code like:
DerivedClass dc = null;
dc = new DerivedClass();
dc = new DerivedClass(42);
dc = new DerivedClass(42,"The Answer");
Unlike a method/function, constructors are not inherited. That's important enough to be said again: Constructors are not inherited. So, of course, our next couple of samples will deal with constructors and inheritence.
Consider the following:
public class BaseClass
{
protected BaseClass()
{
Console.WriteLine("Base Class Instantiated");
}
}
public class DerivedClass : BaseClass
{
}
If you were to compile and run an instance like this:
class Program
{
static void Main(string[] args)
{
DerivedClass dc = null;
dc = new DerivedClass();
Console.ReadLine();
}
}
What should the expected output be? Nothing, since DerivedClass doesn't list a constructor and it doesnt inherit a constructor? How about "Base Class Instantiated"? Yes, indeed. But how since DerivedClass doesn't list a constructor and it doesnt inherit a constructor? Firstly, the .NET Compiler (and Java Compiler, for that matter) gives you a "free" parameterless constructor if your class declares no other constructor. You can confirm this be compiling the code and looking at the result in a tool like
Lutz Roeder's Reflector. In most circles, a parameterless constructor is referred to as a default constructor. It is important to note that you only get this "free" constructor if you have no other constructors declared in the class.
"Ok," you are saying, "this 'free' constructor concept explains why it compiled (because it has a constructor), but it doesn't explain why it outputs what it did, especially if constructors aren't inherited". Constructors are still not inherited, but all the potential non-private members of a class are. How is a child class supposed to know what items it has inherited without an instance of the base class? This is what is happening in this case: by instantiating an instance of the DerivedClass, an instance of the BaseClass must be instantiated and initialized so that the BaseClass members would be available for potential calls from the DerivedClass. This is an important fact to keep in mind, because if you have an object with 5 layers of inheritence (e.g.: great-great-grandparent to great-grandparent to grandparent to parent to child) then each of the inherited members must in turn be constructed and initialized. This is often one of the reasons that object creation can be an expensive operation.
So, what would the output be if we changed the DerivedClass as follows:
public class DerivedClass : BaseClass
{
public DerivedClass()
{
Console.WriteLine("Derived Class Instantiated");
}
}
What would our output be then?
Base Class Instantiated
Derived Class Instantiated
This is the correct output, because when this line runs in our application (dc = new DerivedClass();), the DerivedClass constructor is called, which has to wait for the BaseClass constructor to be called and completed. You can test this via the debugger.
Let's look at the next example:
public class DerivedClass : BaseClass
{
public DerivedClass(string arg)
{
Console.WriteLine("Derived Class Instantiated plus arguement: " + arg);
}
}
This code only has one constructor now, one that takes a string arguement at instantiation. If you attempted to instantiate a DerivedClass instance like this:
DerivedClass dc = null;
dc = new DerivedClass();
compilation would fail. This is because DerivedClass no longer has a default constructor to be used. The default "no-parameter" constructor is only provided by the compiler is no other constructors are listed in the class. This is an issue that becomes more apparent not when a application is first developed, but when its later refactored. I generally see this happen in one of two ways.
When refactoring, you see an empty constructor like this:
public class BaseClass
{
protected BaseClass()
{
}
protected BaseClass(string arg)
{
Console.WriteLine("Base Class Instantiated plus arguement: " + arg);
}
}
So you decide to remove the constructor that doesn't seem to add any value. You may have just potentially broken each class that inherits from this one that needed the default constructor in order to complete proper instantiation. More on how to address and fix this issue later.
The other issue that can be introduced with refactor . . . take this class as example:
public class BaseClass
{
/*
*Other class implementation
* details, like properties,
* methods, and events but no construtor
*/
}
You find that at this point you need to implement a constructor that takes a string and an int. Again, you have broken your down stream child classes. Why? Well, with no constructor listed, the compiler was giving you the default constructor. But as soon as you introduced your constructor that took a string and an int, the compiler no longer gave you the no argument constructor. And down stream child classes that were depending on it will no longer find it available.
In Part II, I will talk about constructor chaining, and in Part III, I go with an example that brings it all together.