class A {
A(int value);
}
class B extends A{
final int foo;
B.one(this.foo) : super(foo); // Works
B.two(int foo) : foo = foo, super(this.foo); // Doesn't work
}
In B.one
, I can easily pass the value of foo
to super
but in B.two
, I can't do that. In both cases, the field foo
is assigned before calling super
, in one case it works and in another it fails. So, the question is at what point the fields are created in the constructor.
Dart constructs objects in two phases: first outside-in and then inside-out.
Initializer lists are executed outside-in (from derived class to base class). After this is done, the object's members should be initialized, the object is considered to be "constructed", and this
exists. (That's why you can't use this
in a meaningful way in an initializer list; it doesn't exist yet.) (Technically direct member initialization occurs before initializer lists, but I'm lumping them together for simplicity.)
Constructor bodies are then executed inside-out (from base class to derived class).
This approach guarantees that all of the object's members are initialized when the base class constructor body executes, allowing virtual dispatch to occur in the constructor body. (In contrast, C++ constructs objects purely inside-out and disallows virtual dispatch in constructors and destructors. Or contrast to, say, Python, where the class mostly defines its own construction order, and the classes are responsible to ensure that any virtual function calls performed by the constructor are safe.)
Without this approach, a Dart constructor body either cannot guarantee safety when executing virtual functions (the Java approach) or must disallow virtual dispatch (the C++ approach).
One consequence of this approach is that final
variables can be initialized by initializer lists but not by constructor bodies: when the constructor body executes, all member variables (that aren't late
and that aren't nullable) are expected to be initialized already.
Also see: