Click here to Skip to main content
15,888,218 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 
And you expect that some future decedent version of the method will validate the object in the context of each of the base classes along the whole hierarchy tree? Or you're controlling the sequencing through that mechanism you described so that doesn't really happen? Insuring proper contextual function calls is more easily handled (and much clearer later) as discussed previously.

You've shown a couple of contrived vanilla examples but no actual real world case where you really need to call a method from the constructor of a base class but have some future unknown derived and overridden version of that method executed. That would make your proposition clearer.

In your most recent example you say you need to do it to validate the object during construction - but what do you need to validate from the constructor? That you wrote the code correctly to properly construct and initialize that class? Any validation you might need to do at any particular level in the inheritance tree construction should reasonably be for that class only - otherwise you've fundamentally breached the whole class encapsulation.

The C# team blew the virtual function handling at constructor time and never fixed it because it was too difficult and they didn't think anyone would do it anyway - if you're going to allow it at all, the only reasonable and consistent constructor vs. virtual method handling is what C++ does (which is the behavior my earlier example has).

But it would be interesting to see a real world necessity for calling the unknowable future derivation of a base class's virtual function from that base class. It might provide better context and support your point.
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 
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.