Is there a difference in how member variables are initialized in Dart?

In your trivial case, it doesn’t matter.

In general, you can initialize instance variables in a few ways:

Inline (field initializers)

class Example1 {
  T x = value;
}

Advantages:

  • Direct, concise.
  • Member will be initialized in all constructors.
  • Can be used to initialize final or non-nullable members.
  • Member is initialized before invoking base class constructors, which is important when the base class constructor calls member functions that are overridden by the derived class.

Disadvantages:

Initializer list

class Example2 {
  T x;

  Example2() : x = value;
}

Advantages:

  • Can be used to initialize final or non-nullable members.
  • Member is initialized before invoking base class constructors, which is important when the base class constructor calls member functions that are overridden by the derived class.
  • Can utilize construction arguments.
  • The initialized variable always refers to a member variable, never to a constructor parameter.

Disadvantages:

  • If the class has multiple constructors, initialization would need to be duplicated, or constructors should redirect to a common constructor.
  • Cannot depend on this since the initialization occurs before this becomes valid (i.e., cannot depend on other instance members).
  • Can initialize only members of the enclosing class. Because initializer lists are executed before invoking base class constructors, they cannot set base class members.

Constructor body

class Example3 {
  T x;

  Example3() {
    x = value;
  } 
}

Advantages:

  • Can utilize construction arguments.
  • Can be used to perform more complicated initialization, such as cases where the member cannot be initialized via a single expression.
  • Can use this (i.e., can use other instance members).
  • Can be used to set base class members.

Disadvantages:

  • Cannot be used to initialize non-late final nor non-nullable members.
  • If the class has multiple constructors, initialization would need to be duplicated or initialization code would need to be refactored out (such as, but not limited to, redirecting to a common constructor).
  • Member is initialized after invoking base class constructors.
  • If the constructor has a parameter that shadows a member variable, it’s easy to accidentally refer to the parameter instead of the member. (See https://github.com/dart-lang/linter/issues/2552 for details.)

There probably are some points I’m forgetting, but I think that should cover the main ones.

Direct, inline initialization occurs first, then initialization lists, then constructor bodies. Also see Difference between assigning the values in parameter list and initialiser list, which explains why this becomes valid only for the later stages of object initialization.

As an example where it matters where members are initialized:

class Base {
  Base() {
    doSomething();
  }

  void doSomething() {}
}

class DerivedEarly extends Base {
  int? x;

  DerivedEarly() : x = 42;

  @override
  void doSomething() => print(x);
}

class DerivedLate extends Base {
  int? x;

  DerivedLate() {
    x = 42;
  }

  @override
  void doSomething() => print(x);
}

void main() {
  DerivedEarly(); // Prints: 42
  DerivedLate(); // Prints: null
}

Leave a Comment