Introduction
The idea is simple - you should make calls to virtual members only from the top of the inheritance hierarchy. This tip shows it can be done in a simple manner.
Background
You can read more about the solving problem here or try to find on the internet - 'virtual member call from constructor'.
Using the Code
Let's suppose we have three simple classes A, B, C inherited in such sequence A -> B -> C.
A has virtual member, B and C override it and use it in their constructors.
Code A: Will cause runtime error, because there are not initialized members in B and C.
class A
{
public A()
{
VirtualMethod();
}
protected virtual void VirtualMethod()
{
Console.WriteLine("A: Do nothing");
}
}
class B : A
{
private readonly String _someString;
public B(String someString)
:base () {
if(someString == null) throw new ArgumentNullException("someString");
_someString = someString;
VirtualMethod();
}
protected override void VirtualMethod()
{
Console.WriteLine("B : Length of some string - {0}", _someString.Length);
}
}
class C : B
{
private readonly String _someString;
public C(String someStringC, String someStringB)
: base(someStringB) {
if (someStringC == null) throw new ArgumentNullException("someStringC");
_someString = someStringC;
VirtualMethod();
}
protected override void VirtualMethod()
{
Console.WriteLine("C : Length of some string - {0}", _someString.Length);
}
}
To solve a problem, we should make some changes to base and inherited classes, these changes will be visible in the following code:
Code B: Will not cause any errors.
class A
{
private List<Action> _virtualMembersCallActions;
protected void CallVirtualMembers<TType>(Action action = null)
{
if (action != null)
{
if (_virtualMembersCallActions == null)
{
_virtualMembersCallActions = new List<Action>();
}
if (!_virtualMembersCallActions.Contains(action))
{
_virtualMembersCallActions.Add(action);
}
}
if (GetType() == typeof (TType))
{
_virtualMembersCallActions.ForEach(i=>i());
_virtualMembersCallActions = null;
}
}
public A()
{
CallVirtualMembers<A>(VirtualMethod);
}
protected virtual void VirtualMethod()
{
Console.WriteLine("A: Do nothing");
}
}
class B : A
{
private readonly String _someString;
public B(String someString)
:base () {
if(someString == null) throw new ArgumentNullException("someString");
_someString = someString;
CallVirtualMembers<B>(VirtualMethod);
}
protected override void VirtualMethod()
{
Console.WriteLine("B : Length of some string - {0}", _someString.Length);
}
}
class C : B
{
private readonly String _someString;
public C(String someStringC, String someStringB)
: base(someStringB)
{
if (someStringC == null) throw new ArgumentNullException("someStringC");
_someString = someStringC;
CallVirtualMembers<C>(VirtualMethod);
}
protected override void VirtualMethod()
{
Console.WriteLine("C : Length of some string - {0}", _someString.Length);
}
}
Of course, this just a sample, you can add any business logic to CallVirtualMembers
function, you can have several such functions to change sequence if virtual members are calling. In my current example, I have added:
if (!_virtualMembersCallActions.Contains(action))
{
_virtualMembersCallActions.Add(action);
}
to suppress repeat calling of same virtual members twice or more times, if you remove this line you will see repeats in console window.
Code C: No errors, but use singleton extension
public static class VirtualsCaller
{
private static readonly Dictionary<Object, List<Action>> _actionsStore = new Dictionary<object, List<Action>>();
public static void CallVirtualMembers<TType>(Object source, Action action)
{
List<Action> actions;
if (!_actionsStore.TryGetValue(source, out actions))
{
actions = new List<Action>();
_actionsStore[source] = actions;
}
if (action != null)
{
if (!actions.Contains(action))
{
actions.Add(action);
}
}
if (source.GetType() == typeof(TType))
{
actions.ForEach(i => i());
_actionsStore.Remove(source);
}
}
}
class A
{
public A()
{
VirtualsCaller.CallVirtualMembers<A>(this, VirtualMethod);
}
protected virtual void VirtualMethod()
{
Console.WriteLine("A: Do nothing");
}
}
class B : A
{
private readonly String _someString;
public B(String someString)
:base () {
if(someString == null) throw new ArgumentNullException("someString");
_someString = someString;
VirtualsCaller.CallVirtualMembers<B>(this, VirtualMethod);
}
protected override void VirtualMethod()
{
Console.WriteLine("B : Length of some string - {0}", _someString.Length);
}
}
class C : B
{
private readonly String _someString;
public C(String someStringC, String someStringB)
: base(someStringB) {
if (someStringC == null) throw new ArgumentNullException("someStringC");
_someString = someStringC;
VirtualsCaller.CallVirtualMembers<C>(this, VirtualMethod);
}
protected override void VirtualMethod()
{
Console.WriteLine("C : Length of some string - {0}", _someString.Length);
}
}
History
- 23/03/2015 - Just created