It's construction time! Read this post if you want to refresh your mind on
how object construction works. In this post, I will focus on instance constructors
in C# and will leave the static constructors (aka type initialisers) to the
next post.
Every time you use the new
keyword to instantiate an object, the instance constructor of the class is called
to initialise the object before it is used.
Each class can have multiple instance constructors with different signatures and
access modifiers. If you do not provide an instance constructor for your
class, C# compiler will create one for you. The access modifier for the default
constructor will be public unless you are defining an abstract class where the access
modifier will be set to 'protected' ('family' in IL terminology).
public
class Customer
{
}
-----------------------------
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Customer::.ctor
This means that all classes have at least one constructor defined when compiled
to IL, with one exception: if you define your class as static, the compiler
will not create a default constructor. In fact, you will get a compile-time error
if you explicitly define an instance constructor for a static class, which
makes sense.
When compiled to IL, instance constructors are emitted as .ctor methods. The
compiler first generates the initialisation code for the instance fields, if there
are any. Instance field initialisers are injected into the very beginning of
the constructor. Fields are inistialised in the same sequence as they are defined
in the class and cannot reference any other instance members (although
they can reference the static members of the class).
Now let's see how instance field initialisers look like when compiled to the IL
code:
public
class Customer
{
public int EmployeeId = 0;
public string DepartmentId =
"";
}
---------------------
.method public hidebysig specialname rtspecialname
instance void
.ctor() cil managed
{
// Code size 26 (0x1a)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: stfld int32 ConstructorSamples.Customer::EmployeeId
IL_0007: ldarg.0
IL_0008: ldstr
""
IL_000d: stfld string ConstructorSamples.Customer::DepartmentId
IL_0012: ldarg.0
IL_0013: call instance void [mscorlib]System.Object::.ctor()
IL_0018: nop
IL_0019: ret
} // end of method Customer::.ctor
You may also notice that Customer's constructor is calling the constructor of System.Object. Before
you can create an instance of any object, its base class needs to be constructed
first. This goes all the way up to System.Object (in this case System.Object
was the immediate parent). So right after emitting the code to initialise the instance
fields, the compiler emits an instruction to call the base class's constructor. In
order to do so, it looks at the definition of the constructor to see whether
it explicitly mentions any specific constructor on the base class (using the base keyword). If it does, it calls
the specified constructor on the base class. Otherwise, it calls the parameterless
constructor on the base class. This is also the case if the child class doesn't
have any constructor. You will get a compile time error if you do not provide a
constructor for your class and the base class does not have a parameterless
constructor.
Instead of using the base keyword,
you can also use this, which allows
you to call another constructor on the same class rather than the base class. You
can chain the constructors on the same class but eventually, the last constructor
in the chain needs to call the base class's constructor.
Something that you need to take into account when overloading the constructor on
a type is that inline field initialisers are injected into evey single contructor
on the class, unless a constructor is using
this keyword to call another constructor on the same class. So the compiled
IL code becomes larger in size if you have lots of field initialisers
in a class with multiple constuctor overloads. In most cases you don't need to be
worried about this but if you are, you can chain the constructors by using
this keyword.
So we saw how compiler injects inline field initialisation logic and the call to
the base class construcor into the IL code. In the last step, the compiler emits
the implementation of the constructor as defined by the developer.
In the next example, the Customer class inherits from Person, which has a parameterised
constructor. Since we have defined the parameterised constructor for Person, the
compiler does not create a default one for us. This means the Customer's constructor
has to specify which constructor should be called on the Person class when an instance
of Customer is being created.
public
class Person
{
public Person(string
knownName)
{
}
}
public
class Customer: Person
{
public int EmployeeId
= 0;
public string DepartmentId
= "";
public Customer(string
firstName, string lastName): base(firstName
+ " " + lastName)
{
Console.WriteLine("Customer
created");
}
}
---------------------
.method public hidebysig specialname rtspecialname
instance void
.ctor(string firstName, string lastName) cil managed
{
// Code size 51 (0x33)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: stfld int32 ConstructorSamples.Customer::EmployeeId
IL_0007: ldarg.0
IL_0008: ldstr ""
IL_000d: stfld string ConstructorSamples.Customer::DepartmentId
IL_0012: ldarg.0
IL_0013: ldarg.1
IL_0014: ldstr " "
IL_0019: ldarg.2
IL_001a: call string [mscorlib]System.String::Concat(string,string,string)
IL_001f:
call instance void ConstructorSamples.Person::.ctor(string)
IL_0024:
nop
IL_0025: nop
IL_0026: ldstr
"Customer created"
IL_002b: call
void [mscorlib]System.Console::WriteLine(string)
IL_0030: nop
IL_0031: nop
IL_0032: ret
>} // end of method Customer::.ctor
So to wrap it up: the compiled code initialises the member variables first, then
calls the base class's constructor and finally emits the actual constructor code.
Myth: Instance constructor are always run before any other method on that
instance.
- Instance constructors are not run when a class is deserialised.
- The overridden methods in the class may be executed before the constructor. This
happens if the constructor of the base class calls any virtual methods that are
overridden in a child class. Let's see how this can happen...
public
class Person
{
public Person()
{
Console.WriteLine("Person created");
Load();
}
protected virtual void
Load()
{
Console.WriteLine("Person loaded");
}
}
---------------------
public class
Employee: Person
{
public Employee()
{
Console.WriteLine("Employee
created");
}
protected override
void Load()
{
Console.WriteLine("Employee
loaded");
}
}
---------------------
Now, if your create an instance of the Employee
class by running the following line of code:
Employee employee = new Employee();
You will get the following result, which may
not be desirable.
Person created
Employee loaded
Employee created
In order to avoid the confusion and potential logical errors, it is a good
development practice not to call a virtual method in the instance constructor. This should
be considered as an anti-pattern for the Template Method
pattern: Using the constructor as the "Template Method" for calling
virtual operations on a base class.