Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Safely Calling Virtual Members From Constructors...

0.00/5 (No votes)
23 Mar 2015 1  
Many programmers know what calling virtual members from not sealed class constructor may cause runtime error, because some members used in virtuals could be not initialized. I will show a simple way to call virtuals safely from constructors of inherited classes.

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 () //Just to show that base constructor called, may be skipped
    {
        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) //Just to show that base constructor called, may be skipped
    {
        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;

    /// <summary>
    /// This function should be called from every inherited  constructor,
    /// if you forget to call it you will have some not initialized
    /// members through all inheritance hierarchy be careful!
    /// </summary>
    /// <typeparam name="TType">Type of currently calling class.</typeparam>
    /// <param name="action">Action which contains virtual members calls.</param>
    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 () //Just to show that base constructor called, may be skipped
    {
        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>>();

    /// <summary>
    /// This function should be called from every inherited  constructor,
    /// if you forget to call it you will have some not initialized
    /// members through all inheritance hierarchy be careful!
    /// </summary>
    /// <typeparam name="TType">Type of currently calling class.</typeparam>
    /// <param name="source">Source object</param>
    /// <param name="action">Action which contains wirtual members calls.</param>
    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 () //Just to show that base constructor called, may be skipped
    {
        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) //Just to show that base constructor called, may be skipped
    {
        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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here