Friday, May 18, 2012

Virtual member call in a constructor


I'm getting a warning from ReSharper about a call to a virtual member from my objects constructor. Why would this be something not to do?



Source: Tips4all

9 comments:

  1. (Assuming you're writing in C# here)

    When an object written in C# is constructed, what happens is that the initializers run in order from the most derived class to the base class, and then constructors run in order from the base class to the most derived class (see Eric Lippert's blog for details as to why this is).

    Also in .NET objects do not change type as they are constructed, but start out as the most derived type, with the method table being for the most derived type. This means that virtual method calls always run on the most derived type.

    When you combine these two facts you are left with the problem that if you make a virtual method call in a constructor, and it is not the most derived type in its inheritance hierarchy, that it will be called on a class whose constructor has not been run, and therefore may not be in a suitable state to have that method called.

    This problem is, of course, mitigated if you mark your class as sealed to ensure that it is the most derived type in the inheritance hierarchy - in which case it is perfectly safe to call the virtual method.

    ReplyDelete
  2. The rules of C# are very different from that of Java and C++.

    When you are in the constructor for some object in C#, that object exists in a fully initialized (just not "constructed") form, as its fully derived type.

    namespace Demo
    {
    class A
    {
    public A()
    {
    System.Console.WriteLine("This is a {0},", this.GetType());
    }
    }

    class B : A
    {
    }

    // . . .

    B b = new B(); // Output: "This is a Demo.B"
    }


    This means that if you call a virtual function from the constructor of A, it will resolve to any override in B, if one is provided.

    Even if you intentionally set up A and B like this, fully understanding the behavior of the system, you could be in for a shock later. Say you called virtual functions in B's constructor, "knowing" they would be handled by B or A as appropriate. Then time passes, and someone else decides they need to define C, and override some of the virtual functions there. All of a sudden B's constructor ends up calling code in C, which could lead to quite surprising behavior.

    It is probably a good idea to avoid virtual functions in constructors anyway, since the rules are so different between C#, C++, and Java. Your programmers may not know what to expect!

    ReplyDelete
  3. In order to answer your question, consider this question: what will the below code print out when the Child object is instantiated?

    class Parent
    {
    public Parent()
    {
    DoSomething();
    }
    protected virtual void DoSomething() {};
    }

    class Child : Parent
    {
    private string foo;
    public Child() { foo = "HELLO"; }
    protected override void DoSomething()
    {
    Console.WriteLine(foo.ToLower());
    }
    }


    The answer is that in fact a NullReferenceException will be thrown, because foo is null. An object's base constructor is called before its own constructor. By having a virtual call in an object's constructor you are introducing the possibility that inheriting objects will execute code before they have been fully initialized.

    ReplyDelete
  4. Reasons of the warning are already described, but how would you fix the warning? You have to seal either class or virtual member.

    class B
    {
    protected virtual void Foo() { }
    }

    class A : B
    {
    public A()
    {
    Foo(); // warning here
    }
    }


    You can seal class A:

    sealed class A : B
    {
    public A()
    {
    Foo(); // no warning
    }
    }


    Or you can seal method Foo:

    class A : B
    {
    public A()
    {
    Foo(); // no warning
    }

    protected sealed override void Foo()
    {
    base.Foo();
    }
    }

    ReplyDelete
  5. In C#, a base class' constructor runs before the derived class' constructor, so any instance fields that a derived class might use in the possibly-overridden virtual member are not initialized yet.

    Do note that this is just a warning to make you pay attention and make sure it's all-right. There are actual use-cases for this scenario, you just have to document the behavior of the virtual member that it can not use any instance fields declared in a derived class below where the constructor calling it is.

    ReplyDelete
  6. Yes, it's generally bad to call virtual method in the constructor.

    At this point, the objet may not be fully constructed yet, and the invariants expected by methods may not hold yet.

    ReplyDelete
  7. Because until the constructor has completed executing, the object is not fully instantiated. Any members referenced by the virtual function may not be initialised. In C++, when you are in a constructor, this only refers to the static type of the constructor you are in, and not the actual dynamic type of the object that is being created. This means that the virtual function call might not even go where you expect it to.

    ReplyDelete
  8. Your constructor may (later, in an extension of your software) be called from the constructor of a subclass that overrides the virtual method. Now not the subclass's implementation of the function, but the implementation of the base class will be called. So it doesn't really make sense to call a virtual function here.

    However, if your design satisfies the Liskov Substitution principle, no harm will be done. Probably that's why it's tolerated - a warning, not an error.

    ReplyDelete
  9. There's a difference between C++ and C# in this specific case.
    In C++ the object is not initialized and therefore it is unsafe to call a virutal function inside a constructor.
    In C# when a class object is created all its members are zero initialized. It is possible to call a virtual function in the constructor but if you'll might access members that are still zero. If you don't need to access members it is quite safe to call a virtual function in C#.

    ReplyDelete