New Design Guideline: Virtual Members

As you might guess from my last few posts, I have been doing some thinking about virtual members. Here are the updated guidelines. Please let me know if you have any questions or comments.  

As always, you can check out the base design guidelines and my incremental updates.

Do not call virtual members from constructors. Calling virtual methods during construction will cause the most derived override to be called, even though the most derived constructor has not been fully run yet. FxCop Rule: ConstructorsShouldNotCallBaseClassVirtualMethods

Consider the example below which prints out “value != 42, what is wrong” when a new instance of Derived is created. The implementer of the derived class assumes that the value will be set to 42 before anyone can call Method1(). But that is not true because the base class’s constructor is called before the Derived class’s constructor finishes, so any calls it makes to Method1() causes the problem.

public class Base

{

    public Base()

    {

        Method1();

    }

    public virtual void Method1() {

        Console.WriteLine("In Base's Method1()");

    }

}

public class Derived: Base

{

    private int value;

    public Derived()

    {

        value = 42;

    }

    public override void Method1()

    {

        if (value == 42) Console.WriteLine("value == 42, all is good");

        else Console.WriteLine("value != 42, what is wrong?");

    }

}

 

When a virtual method is called, the actual type that executes the method is not selected until runtime. When a constructor calls a virtual method there is a chance that the constructor for the instance that invokes the method has not executed.

Annotation : In unmanaged C++, the vtable is updated during the construction so that a call to a virtual function during construction only calls to the level of the object hierarchy that has been constructed.It’s been my experience that as many programmers are confused by the C++ behavior as the managed behavior. The fact is that most programmers don’t think about the semantics of virtual calls during construction / destruction – until they have just finished debugging a failure related to this.

Either behavior is appropriate for some programs and inappropriate for others. Both behaviors can be logically defended. For the CLR, the decision is ultimately based on our desire to support extremely fast object creation.

Annotation: Within a "program" a developer can deal with the behavior that is implemented. If there are different policies chosen in different "programs" interop becomes a serious issue. Therefore any API between separate "programs" (apps, add-ins) should not depend on behavior of virtual methods. C++ direct calls are another manifestation of this.

 

Do not call virtual members from destructors. Calling virtual members will result in possibly surprising behavior because the most derived implementation of the virtual method will be run.
FxCop Rule: TBD (XXX)

Consider the example below, for an instance of derived a C++ developer would expect Base::DoCleanUp() to run with the base class’s finalizer is run however because the runtime virtualizes the call to the runtime type of the instance is used and Derived::DoCleanUp() is called. Notice this issue can be worked around my making DoCleanUp() no virtual or requiring overrides to insert a call to the base classes implementation.

public class Base

{

    public virtual void DoCleanUp() {

        Console.WriteLine("Do Base's Cleanup");

    }

    ~Base()

    {

        DoCleanUp();

    }

}

public class Derived: Base

{

    public override void DoCleanUp()

    {

        Console.WriteLine("Do Derived Cleanup");

    }

    ~Derived()

    {

        DoCleanUp();

    }

}