Inheritance is a great feature of any object-oriented language. C# adds some modern flair with the ability to have inline constructor chains as well as constructor bodies and optional parameters. This has led to a source of bugs in a number of code-bases in which developers are transitioning from another language.
One such bug involves the use of constructor chains on base and dervied classes - along with constructor bodies in instantiation of the derived class.
Observe the following example:
class BaseClass
{
protected int _index = 0;
public virtual int Index
{
get
{
return _index;
}
set
{
if (_index != value)
{
_index = value;
Console.WriteLine("BaseClass.set_Index(" + Index + ") called");
}
}
}
private BaseClass()
{
Console.WriteLine("BaseClass() constructor called");
}
public BaseClass(int index = 1) : this()
{
Console.WriteLine("BaseClass(int index) constructor called");
Index = index;
}
}
class DerivedClass : BaseClass
{
public virtual string Title { get; set; } = string.Empty;
public override int Index
{
set
{
if (_index != value)
{
_index = value;
Console.WriteLine("DerivedClass.set_Index(" + Index + ") called");
}
}
}
public DerivedClass(string title = null, int index = 2) : base()
{
Console.WriteLine("DerivedClass(string title, int index) constructor called");
Index = index;
Title = title;
}
public static void Main()
{
BaseClass dc = new DerivedClass(index: 3) // Constructor
{
Index = 4, // Constructor body
Title = "Demo Title"
};
// Just because we want to know what happens when we upcast
(dc as BaseClass).Index = 5;
}
}
Running the above code produces the following results:
BaseClass() constructor called BaseClass(int index) constructor called DerivedClass.set_Index(1) called DerivedClass(string title, int index) constructor called DerivedClass.set_Index(3) called DerivedClass.set_Index(4) called DerivedClass.set_Index(5) called
Wait, what happened?
In this case, the optional index value was not set, and so the default value of 1 was passed into the base constructor.
The private parameterless base constructor was called in the base class's optional parameter constructor chain.
The fix is to change
public DerivedClass(string title = null, int index = 2) : base()
to
public DerivedClass(string title = null, int index = 2) : base(index)
or (somewhat redundantly, but more self-documenting)
public DerivedClass(string title = null, int index = 2) : base(index: index)
Making the change correctly calls the base constructor which is passed the integer value of 3.
BaseClass() constructor called BaseClass(int index) constructor called DerivedClass.set_Index(3) called DerivedClass(string title, int index) constructor called DerivedClass.set_Index(4) called DerivedClass.set_Index(5) called
Bug: fixed!