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

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

Rate This
  • Comments 42

As you might have figured out, the answer to last week's puzzle is "if the constructors and initializers run in their actual order then an initialized readonly field of reference type is guaranteed to be non null in any possible call. That guarantee cannot be met if the initializers run in the expected order."

Suppose counterfactually that initializers ran in the expected order, that is, derived class initializers run after the base class constructor body. Consider the following pathological cases:

class Base
{
    public static ThreadsafeCollection t = new ThreadsafeCollection();
    public Base()
    {
        Console.WriteLine("Base constructor");
        if (this is Derived) (this as Derived).DoIt();
        // would deref null if we are constructing an instance of Derived
        Blah();
        // would deref null if we are constructing an instance of MoreDerived
        t.Add(this);
        // would deref null if another thread calls Base.t.GetLatest().Blah();
        // before derived constructor runs
    }
    public virtual void Blah() { }
}
class Derived : Base
{
    readonly Foo derivedFoo = new Foo("Derived initializer");
    public DoIt()
    {
        derivedFoo.Bar();
    }
}
class MoreDerived : Derived
{
    public override void Blah() { DoIt(); }
}

Calling methods on derived types from constructors is dirty pool, but it is not illegal. And stuffing not-quite-constructed objects into global state is risky, but not illegal. I'm not recommending that you do any of these things -- please, do not, for the good of us all. I'm saying that it would be really nice if we could give you an ironclad guarantee that an initialized readonly field is always observed in its initialized state, and we cannot make that guarantee unless we run all the initializers first, and then all of the constructor bodies.

Note that of course, if you initialize your readonly fields in the constructor, then all bets are off. We make no guarantees as to the fields not being accessed before the constructor bodies run.

Next time on FAIC: how to get a question not answered.

 

  • It is conceptually bizarre that the type of an object varies throughout its lifetime. It is also conceptually bizarre that you can end up with an instantiated object of an abstract type that is not of a more specific concrete type.  

    The cure is worse than the disease, in this case.  In C#, objects always have the type that you asked for from the moment of their creation, and you will never see an instance of an abstract type that is not also of a concrete type.  The C++ way of solving this problem is just plain weird.  I'm not aware of any other OOP language that does it that way.

  • I recently ran Analyze over a core library and found that by base constructor was assigning a value to an abstract property. ie. Calling a virtual method. By design, my derived class wanted to maintain the private field which doesn't seem such a good idea anymore when the property is to be accessible via the base class. Refactoring meant relocating the private field to the base class, providing a vitual base implementation for the puiblic property and then overwridding this in the derived class to get the behaviour I needed.

    Analyze gave no more than a warning. Yet I was very impressed to get that much.

  • > It is conceptually bizarre that the type of an object varies throughout its lifetime.

    I don't see that, myself. When one constructs, say, a Door, there's a period in time when it's just an Aperture (which for the purpose of this example is the base class of Door). During that period, if I try to travelThrough() the object, I want to do the Aperture thing, not the Door thing (the latter would fail, since the object has no handle to pull yet).

    Or, look at it another way. Let's view a type as a set of values. Base class X is a pair of ints (a, b), such that a <= b. Derived class Y has another int c, such that a <= c <= b. Let's suppose we construct an object of eventual type Y, passing in (3, 5, 4) as (a, b, c). First, an X base object is constructed with values (3, 5). This base object is of type X, since it's in the set of possible Xs. But the complete object is not of type Y yet, since (in CLR) the value of (3, 5, 0) is not in the set of possible Ys. Only once Y's constructor has run and set c such as to enforce Y's invariant is the complete object in the set of possible Ys and as such of type Y.

    > It is also conceptually bizarre that you can end up with an instantiated object of an abstract type that is not of a more specific concrete type.

    Yes, that certainly goes against a lot of conventional teaching ("abstract means not instantiatable"). But that's the cost of allowing constructors and destructors to enforce class invariants. Which (for C++ at least) is invaluable.

    On the other hand, I find it conceptually bizarre to allow methods on an object of type D to be called before D's constructor is called, and before D's members have been constructed.

  • Thanks Eric. This was a useful refresher for me.

    Sure, C++ does things differently and I still have a lot of fondness for that language even if I get to use it less and less these days. But I'm just as happy with how Java/.Net do things. I think the important thing here is to know how the language your using works, what the limits are and what the benefits are.

    I don't think that either method is universally right or wrong, just different.

    Eric should be commended for his persaverance with this discussion.

  • Richard brings up a potentially elegant solution. Within the body of the constructor, calls to virtual methods on the object being constructed could be treated by the CLR as non-virtual. Then again, this doesn't solve the multi-threading issue that Eric pointed out.

  • Eric: I think you should probably put an explicit disclaimer in the article that mentions that this scheme isn't what VB.Net follows, something I've previously discovered to my cost...

    [I've assumed that the difference is just that VB.Net doesn't really use field initializers, because they have looser rules on what can be used in them, and instead just roll all that stuff into the constructors. As a result the order of 'field initializers' effectively gets reversed from C#. But that's a educated guess not even backed by bothering to look at the IL, so don't hold me to it]

  • I think in the C# model of class initialisation can be (at least) a two stage rocket.

    Construction I : the readonly, private  and public members

    Construction II: the constructor.

    I & II have their own precedence and relationship "rules" vis-a-vis their class and other

    classes (in their inheritance tree or outside it).

    Not meeting 'Type Theory' might be a plus (as indicated by others).

    Agree with what others write about calling methods on a class whose constructor

    hasnt run...  Code is not written.  It is rewritten.  And refractoring on a framework

    with such stuff  (that is usually not even commented) can be time consuming

    because its bug prone.

  • Question: Can somebody provide a real life (non-pathological) example when all this (order of member initialization) matters?

    Opinion: 1) One must never provide a language feature that would require that much confused discussion. 2) If such highly-arguable feature has been provided one must never write code which uses that feature.

    Conclusion: never write code that depends on order of member initialization.

  • 我们在实现类的继承时,创建派生类的实例时,基类与派生类的实例字段都要进行实例化,他们的构造函数都需要调用,那执行的顺序是怎样的呢?一起来做做这个测试题吧。

  • [/qoute]

    Richard said:

    Carrying on from the previous article... I think the C++ model of (as you put it) "objects that mutate their own runtime type" is appropriate. The constructor is what makes an object of a type. Until the constructor is run, the type's invariants aren't met (a type theorist would say that it's not yet of that type). Once the destructor has run, the invariant is once again not met (it's no longer of the fully-derived type). This leads to some surprises (pure virtual function calls being the obvious ones).

    Richard said:

    Carrying on from the previous article... I think the C++ model of (as you put it) "objects that mutate their own runtime type" is appropriate. The constructor is what makes an object of a type. Until the constructor is run, the type's invariants aren't met (a type theorist would say that it's not yet of that type). Once the destructor has run, the invariant is once again not met (it's no longer of the fully-derived type). This leads to some surprises (pure virtual function calls being the obvious ones).

    On the other hand, to me, the CLR model is deeply weird. As I understand things, even before my constructor runs, my member functions can be called, and my member variables can be read or even changed. Any hope of a well-defined notion of a class invariant is lost. Now, you could argue that the problem is the same in both cases -- that essentially, trying to treat a not-yet-constructed object as its derived type before the derived constructor is run is simply an error -- but I would disagree. In C++, you can't get into trouble without explicitly (static_)casting to the derived class, but in C#, you can get into trouble if you call a virtual function from the base class's constructor.

    On the other hand, to me, the CLR model is deeply weird. As I understand things, even before my constructor runs, my member functions can be called, and my member variables can be read or even changed. Any hope of a well-defined notion of a class invariant is lost. Now, you could argue that the problem is the same in both cases -- that essentially, trying to treat a not-yet-constructed object as its derived type before the derived constructor is run is simply an error -- but I would disagree. In C++, you can't get into trouble without explicitly (static_)casting to the derived class, but in C#, you can get into trouble if you call a virtual function from the base class's constructor.[/qoute]

    I think Richard meant theat .NET C# spec goes a CRTP pattern way where C++ does not. This is more intuitive, because it makes no deeper sense to treat an abstract classes (interface) like a more derived, for which .NET has the interface keyword to more distinguish between them.

    [qoute]

    Alex Simkin said:

    To Eric: If constructor is just a method, why bother to have constructors at all?/qoute]

    Hehe... ctors are methods, methods are function with a this calling convention (ecx, rcx has pointer to class data on the stack or Heap), and functions are loosely linked only at compile time (association) are seen some time later in a page mixed with our data which was separated before to have one principal of object oriented design philosophy :-9.

    Object orientation is just a way to fool an asm progger and do the same another way :-)

    Martin

  • I think the problem lies in the fact that the initializers are in a sense constructors. Basically there are 2 types of constructors, running in opposite directions. I am not sure what real bennefit bring the initializers. Don't tell me it makes code shorter because it sounds like a bad joke. Readonly members? There are alternatives.

    You don't have to duplicate code because you can create a private method that initializes the members. It cannot be overriden, it gives no headaches. You call it from all of the constructors and all is nice and leads to no contradictions or unexpected situations. As for calling methods of the derived class from the constructor, you can do it in C++ and it is idiotic. I don't think this should trigger big changes in the language but rather in the brain of the guy who does it.

  • Sorry Eric, but I you haven't explained a solution to the actual problem. The interesting problem was not why constructors should be invoked after initialisers, I think everyone with a bit of analytical insight agrees to this being a sensible design decision. The actual problem was why initialisation should be done in a certain order.

    My conjecture here is that it doesn't really matter much in which order initialisations are carried out as long as they are performed before all constructors, meaning that the order if initialisation is not a serious source for ambiguity and unexpected behaviour. The reason for this, I would say, is that prior to initialisation the object is not observable: it might already physically exists in memory but no reference to it is exposed yet. Clearly, the aggregated objects should besides have no knowledge of the context in which they are initialised either. The exposure of the object reference is what creates ambiguities and unexpected behaviour, and the earliest that happens is when the (first) constructor is called. Prior to this we cannot observe what happens, at least if confining our observation to the state of the object to be instantiated, thus it is irrelevant from a logical point of view. This is maybe not quite true, I could imagine pathological cases where instantiation of the aggregated objects might have some side effect that depends on their order, but this would have to involve some global state and seems rather contrived.

    I'm not really a (C#) programmer so might be missing something; if anyone had a genuine example where the order of initialisation prior to constructor invocation is observable in the sense described above, and more importantly creates potential for misunderstanding and errors of the sort that happen when interleaving initialisation and constructor calls, that would be of interest to justify this design decision. Btw even if there is no favourable order it may still make sense to impose one to avoid unintentional exploitation of unspecified behaviour.

    Best,

    Frank

Page 3 of 3 (42 items) 123