Dart assigning to variable right away or in constructor?

Solution 1:

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:

  • Cannot depend on construction arguments.
  • Usually cannot depend on this since the initialization occurs before this becomes valid (i.e., cannot depend on other instance members). (An exception is if the member is initialized lazily by declaring it late. This requires the null-safety feature to be enabled.)

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 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
}