Click here to Skip to main content
15,886,873 members
Articles / Programming Languages / C#
Tip/Trick

Safely Calling Virtual Members From Constructors...

Rate me:
Please Sign up or sign in to vote.
2.86/5 (5 votes)
23 Mar 2015CPOL1 min read 17.9K   5   18
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.

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.

C#
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:

C#
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

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Saber Interactive
Russian Federation Russian Federation
My specializations:

C# (especially multithreading)
WPF (MVVM, styling)
WCF (message inspectors, configuration)
MSSQL (administartion, creation, procedures, recursive queries, bulk processing)

Comments and Discussions

 
QuestionSeparate construction/new from initialization Pin
englebart24-Mar-15 11:08
professionalenglebart24-Mar-15 11:08 
AnswerRe: Separate construction/new from initialization Pin
Evgeny Bestfator24-Mar-15 21:08
professionalEvgeny Bestfator24-Mar-15 21:08 
AnswerRe: Separate construction/new from initialization Pin
Evgeny Bestfator24-Mar-15 21:10
professionalEvgeny Bestfator24-Mar-15 21:10 
GeneralRe: Separate construction/new from initialization Pin
Evgeny Bestfator24-Mar-15 21:10
professionalEvgeny Bestfator24-Mar-15 21:10 
GeneralRe: Separate construction/new from initialization Pin
ArchAngel12325-Mar-15 10:35
ArchAngel12325-Mar-15 10:35 
GeneralRe: Separate construction/new from initialization Pin
Evgeny Bestfator25-Mar-15 22:46
professionalEvgeny Bestfator25-Mar-15 22:46 
GeneralRe: Separate construction/new from initialization Pin
englebart6-Apr-15 2:11
professionalenglebart6-Apr-15 2:11 
GeneralRe: Separate construction/new from initialization Pin
Evgeny Bestfator6-Apr-15 12:08
professionalEvgeny Bestfator6-Apr-15 12:08 
SuggestionSimpler solution Pin
ArchAngel12324-Mar-15 10:28
ArchAngel12324-Mar-15 10:28 
GeneralRe: Simpler solution Pin
Evgeny Bestfator24-Mar-15 21:38
professionalEvgeny Bestfator24-Mar-15 21:38 
GeneralRe: Simpler solution Pin
ArchAngel12325-Mar-15 10:29
ArchAngel12325-Mar-15 10:29 
GeneralRe: Simpler solution Pin
Evgeny Bestfator25-Mar-15 23:05
professionalEvgeny Bestfator25-Mar-15 23:05 
GeneralComplex solution to a problem that shouldn't exist Pin
John Brett24-Mar-15 0:22
John Brett24-Mar-15 0:22 
GeneralRe: Complex solution to a problem that shouldn't exist Pin
Evgeny Bestfator24-Mar-15 0:51
professionalEvgeny Bestfator24-Mar-15 0:51 
GeneralRe: Complex solution to a problem that shouldn't exist Pin
John Brett24-Mar-15 1:50
John Brett24-Mar-15 1:50 
GeneralRe: Complex solution to a problem that shouldn't exist Pin
Evgeny Bestfator24-Mar-15 2:24
professionalEvgeny Bestfator24-Mar-15 2:24 
QuestionNice try... Pin
FatCatProgrammer23-Mar-15 5:10
FatCatProgrammer23-Mar-15 5:10 
However why would I complicate my class when I could just not call the virtual members? You're also adding a list in there. I just see potential issues.
Relativity

AnswerRe: Nice try... Pin
Evgeny Bestfator23-Mar-15 21:10
professionalEvgeny Bestfator23-Mar-15 21:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.