Thanks Eric Lippert's comprehensive comments in the comment section! I learn a lot from his comments. To share my learning with future readers, I've used parenthesis to wrap the wrong assumptions in my original question below so you can compare. And I have put the corrected assumptions in bold.
I read through this post Virtual member call in a constructor, and understand that we should not call overriddable methods in base class constructor, because the base class constructor (wrong: will invoke the virtual member in the base class when we want it to invoke the overridden member in the subclass) will invoke the overridden member in the subclass, but before subclass has fully initialized.
Now I have a base class with an abstract property, (wrong: not a virtual method), which is a still virtual member except that it must be overridden, and I'm using it in my constructor. Then I have my subclass overriding this property. My expectation is that when I create a new instance out of my subclass, the base constructor will be using the overridden property in the subclass. And this is what actually happened.
I'm wondering if this is a good practice, because I still get the same warning saying "Virtual member call in a constructor", (wrong: although technically I'm not using virtual members). I don't see any obvious anti-pattern here, the only potential one is that if I further subclass my existing subclass, things might get confusing, but I'm marking my first subclass sealed
.
Below is an executable code sample, it will output: Name1, Name3
And I get the above warning in the base constructor line this.NeededNames.Contains(name)
So my code sample is simple enough to work because its virtual member NeededNames
does not rely on anything in the TestNameList
class. But as Eric explained in comments, if a subclass happens to have states that are depended by its abstract/virtual members, when the base constructor runs, the overridden member in the subclass will be called, but it will not be ready yet because the states have not been set up by the subclass constructor. In this case, the subclass instance would not be initialized properly.
This sample is not a good practice although it works, because in reality we don't know what subclass could others create from our base class, and if a new subclass has states that need to be set up during the subclass construction, then we would not be able to instantiate it correctly from the base class constructor. Outside of this question, to make this sample work in a better way, I created an Initialize method in the base class that will be invoked after every subclass is instantiated.
public class Program
{
public static void Main()
{
NameListBase list = new TestNameList();
list.Test();
}
}
public abstract class NameListBase
{
protected IList<string> ActualNames { get; } = new List<string>();
IList<string> Names { get; } = new List<string>
{
"Name1", "Name2", "Name3", "Name4"
};
protected abstract IList<string> NeededNames { get; }
protected NameListBase()
{
foreach (string name in this.Names)
{
if (this.NeededNames.Contains(name))
{
this.ActualNames.Add(name);
}
}
}
public abstract void Test();
}
public sealed class TestNameList : NameListBase
{
protected override IList<string> NeededNames { get; } = new List<string>
{
"Name1", "Name3"
};
public override void Test()
{
Console.WriteLine(string.Join(", ", this.ActualNames));
}
}