Why Do Initializers Run In The Opposite Order As Constructors? Part One

Why Do Initializers Run In The Opposite Order As Constructors? Part One

Rate This
  • Comments 36

Pop quiz!

What do you expect the output of this program to be?

 

using System;

class Foo
{
    public Foo(string s)
    {
        Console.WriteLine("Foo constructor: {0}", s);
    }
    public void Bar() { }
}
class Base
{
    readonly Foo baseFoo = new Foo("Base initializer");
    public Base()
    {
        Console.WriteLine("Base constructor");
    }
}
class Derived : Base
{
    readonly Foo derivedFoo = new Foo("Derived initializer");
    public Derived()
    {
        Console.WriteLine("Derived constructor");
    }
}
static class Program
{
    static void Main()
    {
        new Derived();
    }
}

I got a question from a user recently noting that the order was not as he expected. One naively expects that the order will go "base initializers, base constructor body, derived initializers, derived constructor body". In fact the order actually is that first all the initializers run in order from derived to base, and then all the constructor bodies run in order from base to derived.

The latter bit makes perfect sense; the more derived constructors may rely upon state initialized by the less derived constructors, so the constructors should run in order from base to derived. But most people assume that the call sequence of the code above is equivalent to this pseudocode:

// Expected
BaseConstructor()
{
    ObjectConstructor();
    baseFoo = new Foo("Base initializer");
    Console.WriteLine("Base constructor");
}
DerivedConstructor()
{
    BaseConstructor();
    derivedFoo = new Foo("Derived initializer");
    Console.WriteLine("Derived constructor");
}

When in point of fact it is equivalent to this:

 // Actual
BaseConstructor()
{
    baseFoo = new Foo("Base initializer");
    ObjectConstructor();
    Console.WriteLine("Base constructor");
}
DerivedConstructor()
{
     derivedFoo = new Foo("Derived initializer");
    BaseConstructor();
    Console.WriteLine("Derived constructor");
}

That explains the mechanism whereby the initializers run in order from derived to base and the constructor bodies run in the opposite order, but why did we choose to implement that mechanism instead of the more intuitively obvious former way?

Puzzle that one over for a bit, and then read on for a hint.

...

...

...

The "readonly" modifiers in there were no accident. The code gives the appearance that any call to derivedFoo.Bar() and baseFoo.Bar() should never fail with a null dereference exception because both are readonly fields initialized to non-null values.

  1. Is that appearance accurate, or misleading?
  2. Now suppose initializers ran in the "expected" order and answer question (1) again. 

I'll post the answers and analysis next week. Have a fabulous weekend!

 

  • Possible spoilers by implication, so ROT-13.

    V'ir nyjnlf yvxrq vg gur jnl vg vf - orpnhfr V'ir rkcrevraprq gur cnva bs vg orvat gur bgure jnl ebhaq.

    Gur rdhvinyrag (va grezf bs fbhepr) Wnin cebtenz jevgrf:

    Sbb pbafgehpgbe: Onfr vavgvnyvmre

    Onfr pbafgehpgbe

    Sbb pbafgehpgbe: Qrevirq vavgvnyvmre

    Qrevirq pbafgehpgbe

    Guvf orunivbhe pnhfrq n irel uneq-gb-qvntabfr reebe va n pbyyrnthr'f pbqr znal zbbaf ntb. Vg graqf abg gb znggre vs lbh qba'g znxr iveghny pnyyf jvguva gur onfr pbafgehpgbe, ohg gung'f rknpgyl jung gur zbfg angheny qrfvta pnyyrq sbe va guvf pnfr. (Gung jnf jura V svefg yrnearq gung vg'f n onq vqrn gb znxr iveghny pnyyf sebz n pbafgehpgbe!)

  • The most obvious thing that comes to my mind is virtual method calls in ancestor constructors. Bad practice in any case, though sometimes it's the most natural solution.

  • Actually implementation is rather confusing.

    Make baseFoo and derivedFoo fields static and run the application. Derived will be initialized before base as with non-static fields.

    Then, modify constructor of Foo to take reference to Foo and pass baseFoo to derivedFoo constructor. The initialization sequence will change to Base first, then Derived.

    Confusing...

  • Ah, yes. I should have mentioned that. The rules for static constructors and static fields are subtly different, and I do not intend this article to discuss anything about the rules for statics. That's a great topic for a follow-up article.

  • 1. Accurate.

    2. Misleading. [Waves to James Gosling.]

  • Just a note. VB.NET does initialization differently.

  • I suspect there is a good reason behind every 'confusing' behavior... I think I know why the following program has to behave that way but I find it confusing nevertheless.

    class Program {

           static void Main() {

               B b = new B();

               b.Display();

               b.OtherDisplay();

           }

       }

       class A {

            public void Display() {

               Console.WriteLine("from A");

           }

       }

       class B : A {

           new void Display() {

               Console.WriteLine("from B");

           }

           public void OtherDisplay() {

               Display();

           }

       }

  • I'll make a guess:

    If it's done the 'obvious' way, there exists a point (during the base constructor) where the derived readonly field exists but is 0. There exists a later point where the same (readonly) field has a different value. That's presumably not good.

    If it's done the other way around, then by the time any code runs which has access to the new object (the ObjectConstructor), all the readonly fields are fully initialized.

    I'll guess further that this behaviour is due to some kind of weirdness in C# whereby the object's dynamic type is the type of the full object throughout construction, rather than the dynamic type changing as more constructors get called, as happens in C++.

  • Correct, though your characterization of the CLR semantics for the dynamic type of an object as "weirdness" is itself somewhat weird. I would argue that it is more weird for the dynamic type of an object to change throughout that object's lifetime!  The type of an object is a constant from the moment it is created through to the moment it is destroyed. Mutable objects are hard enough to reason about; objects that mutate their own runtime type are truly odd.

  • A useful addition to C# would be to allow field initializers in true C++ fashion:

    public class Stack<T>

    {

       private T[] items;

       private int count;

       public Stack<T>(int capacity) : items(new T[capacity]) { }

       // ...

    }

    This corresponding IL code is currently tolerated by the CLR, and it solves several problems with assertions regarding nullability of fields.

  • class Base

    {

    readonly int baseFooValue = 1;

    public Base()

    {

      Console.WriteLine(Foo().ToString());

    }

    protected virtual int Foo()

    {

     return baseFooValue ;

    }

    }

    class Derived : Base

    {

    readonly derivedFooValue = 2;

    public Derived()

    {

    }

    protected override int Foo()

    {

     return derivedFooValue ;

    }

    }

    This will print "2" but with the expected behavior it would print "0"!

  • Removing readonly modifier does not make any difference in the result. Am I missing something?

  • It's interesting to note that in VB .NET this isn't confusing at all.  The syntax makes it clear what happens.  If you explicitly call the base constructor, and you do it like this...

    Public Sub New()

       ' some other code, and then:

       MyBase.New()

    End Sub

    ...the compiler tells you the base constructor call has to be the first line.  Which makes it obvious, since it looks like a method call, that the base constructor's body gets executed first.

    The C# syntax, unfortunately, is a bit more obtuse to those not familiar with it.

  • Welcome to the forty-first Community Convergence. The big news this week is that we have moved Future

  • Wouldn't the following order make the most sense (since base initializers have no way of referring to derived variables, but the reverse isn't true)?  Or can you provide a counter-example?

    base initializer

    derived initializer

    base constructor

    derived constructor

Page 1 of 3 (36 items) 123