dart-use-primary-constructors

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Dart Primary Constructors & New Constructor Syntax Skill

Dart Primary Constructors及新构造函数语法技能

Use this skill when helping users write, refactor, or debug code using Dart's Primary Constructors feature.
当帮助用户使用Dart的Primary Constructors特性编写、重构或调试代码时,可使用本技能。

Dart Version Requirements

Dart版本要求

  • Dart 3.13 and above: Primary constructors are enabled by default.
  • Dart 3.12: The feature is available but experimental. Users must explicitly enable the experiment flag
    primary-constructors
    via
    --enable-experiment=primary-constructors
    or in
    analysis_options.yaml
    :
yaml
analyzer:
  enable-experiment:
    - primary-constructors
  • Dart 3.11 and earlier: Primary constructors are not supported.

  • Dart 3.13及以上:Primary Constructors默认启用。
  • Dart 3.12:该特性可用但处于实验阶段。用户必须通过
    --enable-experiment=primary-constructors
    或在
    analysis_options.yaml
    中显式启用实验标志
    primary-constructors
yaml
analyzer:
  enable-experiment:
    - primary-constructors
  • Dart 3.11及更早版本:不支持Primary Constructors。

1. Overview

1. 概述

Primary Constructors allow developers to declare a non-redirecting generative constructor as well as a set of instance variables directly in the class header. This significantly reduces boilerplate and improves code readability.
Primary Constructors允许开发者在类头中直接声明一个非重定向生成构造函数以及一组实例变量。这大幅减少了样板代码并提升了代码可读性。

Key Benefits

核心优势

  • Combines field declaration, parameter declaration, and initialization into a single declaration known as a declaring parameter declaration.
  • Enables safe reference to constructor parameters in non-late field initializers (Primary Initializer Scope).
  • Allows empty declaration bodies to be represented concisely with a semicolon (
    ;
    ).
  • Introduces abbreviated concise syntax for in-body constructors.

  • 将字段声明、参数声明和初始化合并为单个声明(称为声明式参数声明)。
  • 支持在非late字段初始化器中安全引用构造函数参数(Primary Initializer Scope)。
  • 允许使用分号(
    ;
    )简洁表示空声明体。
  • 为体内构造函数引入了简洁缩写语法。

2. Syntax Reference

2. 语法参考

2.1 Basic Class Header Syntax

2.1 基础类头语法

To declare a primary constructor, place a parameter list immediately after the type name (and optional type parameters):
dart
// Declares fields x and y, and a generative constructor Point(this.x, this.y)
class Point(var int x, var int y);

// Declares final fields
class PointFinal(final int x, final int y);
要声明Primary Constructor,需在类型名称(及可选类型参数)后紧跟参数列表:
dart
// Declares fields x and y, and a generative constructor Point(this.x, this.y)
class Point(var int x, var int y);

// Declares final fields
class PointFinal(final int x, final int y);

2.2 Declaring, Initializing, and Plain Parameters

2.2 声明式、初始化式与普通参数

A primary constructor parameter list distinguishes between three types of parameters:
  1. Declaring Parameters: Indicated by the
    var
    or
    final
    modifier (e.g.,
    final int x
    ). They implicitly create a corresponding instance field in the class.
  2. Initializing Parameters: Indicated by the
    this.
    or
    super.
    prefix (e.g.,
    this.x
    or
    super.x
    ). They initialize an existing field or a super constructor parameter, respectively.
  3. Regular Parameters: Declared without modifiers (e.g.,
    int y
    ). They do not become fields and are only available during initialization (e.g., in field initializers or the
    this :
    initializer list in the class body).
dart
// `x` is a field and a parameter because it has the keyword `final`. In particular, we can use the name `x` in the initializer list in the in-body part of the primary constructor. 'y' is a only parameter because it has neither of the keywords `final` or `var`, but `y` is passed to the super constructor via the `this :` initializer list.
class C(final int x, int y) extends Base {
  this : super(y);
}
Declaring parameters and initializing parameters are two ways of achieving the same goal: declaring a class with instance fields which are set in the constructor. Regular parameters are different in that their values are not automatically routed to an instance field.
Primary Constructor的参数列表区分三种参数类型:
  1. 声明式参数:由
    var
    final
    修饰符标识(例如
    final int x
    )。它们会在类中隐式创建对应的实例字段。
  2. 初始化式参数:由
    this.
    super.
    前缀标识(例如
    this.x
    super.x
    )。它们分别用于初始化现有字段或父类构造函数参数。
  3. 普通参数:无修饰符声明(例如
    int y
    )。它们不会成为字段,仅在初始化阶段可用(例如字段初始化器或类体中的
    this :
    初始化列表)。
dart
// `x` is a field and a parameter because it has the keyword `final`. In particular, we can use the name `x` in the initializer list in the in-body part of the primary constructor. 'y' is a only parameter because it has neither of the keywords `final` or `var`, but `y` is passed to the super constructor via the `this :` initializer list.
class C(final int x, int y) extends Base {
  this : super(y);
}
声明式参数和初始化式参数是实现同一目标的两种方式:声明一个在构造函数中设置实例字段的类。普通参数则不同,它们的值不会自动路由到实例字段。

2.3 Constant Primary Constructors

2.3 常量Primary Constructor

To make a primary constructor
const
, place the
const
keyword before the class/type name in the declaration header:
dart
class const Point(final int x, final int y);
extension type const Ext(int x);
enum const MyEnum(final int x) {
  entry(1);
}
要将Primary Constructor设为
const
,需在声明头中的类/类型名称前添加
const
关键字:
dart
class const Point(final int x, final int y);
extension type const Ext(int x);
enum const MyEnum(final int x) {
  entry(1);
}

2.4 Extension Types

2.4 扩展类型(Extension Types)

Extension types must use primary constructors.
  • The single parameter in the header is the representation field.
  • The representation variable cannot use the
    var
    modifier (using
    var
    triggers the
    representation_field_modifier
    error).
  • The representation variable can optionally use the
    final
    modifier. If
    final
    is not present then it is inferred; that is, the parameter is declaring whether or not it's explicitly
    final
    .
扩展类型必须使用Primary Constructor。
  • 头中的单个参数是表示字段。
  • 表示变量不能使用
    var
    修饰符(使用
    var
    会触发
    representation_field_modifier
    错误)。
  • 表示变量可选择性使用
    final
    修饰符。如果未指定
    final
    ,则会自动推断;即参数会明确声明是否为
    final

2.5 Empty Body Semicolon Shorthand (
;
)

2.5 空体分号简写(
;

When a class, mixin class, mixin, extension or extension type has an empty body, the
{}
braces can be replaced by a semicolon (
;
):
dart
class C(int x);
mixin class MC;
extension type ET(int x);
mixin M;
extension Ext on C;
当类、mixin类、mixin、扩展或扩展类型的体为空时,
{}
大括号可替换为分号(
;
):
dart
class C(int x);
mixin class MC;
extension type ET(int x);
mixin M;
extension Ext on C;

2.6 The In-Body Part of a Primary Constructor (
this ...
)

2.6 Primary Constructor的体内部分(
this ...

If a primary constructor requires assertions or custom field initializations, they can be declared in the body using the
this :
syntax:
dart
class Point(var int x, var int y) {
  // Initializer list in class body
  this : assert(x >= 0), y = y * 2;
}
You can also write a constructor body with this syntax (
this {...}
).
如果Primary Constructor需要断言或自定义字段初始化,可使用
this :
语法在体中声明:
dart
class Point(var int x, var int y) {
  // Initializer list in class body
  this : assert(x >= 0), y = y * 2;
}
你也可以使用此语法编写构造函数体(
this {...}
)。

2.7 Abbreviated Concise Constructor Syntax

2.7 简洁缩写构造函数语法

For constructors declared within the class body, the class name can be omitted and replaced with the
new
or
factory
keywords:
Traditional SyntaxAbbreviated Concise Syntax
MyClass() {}
new() {}
MyClass.name() {}
new name() {}
const MyClass();
const new();
const MyClass.name();
const new name();
factory MyClass() => ...
factory() => ...
factory MyClass.name() => ...
factory name() => ...

对于在类体中声明的构造函数,可省略类名,替换为
new
factory
关键字:
传统语法简洁缩写语法
MyClass() {}
new() {}
MyClass.name() {}
new name() {}
const MyClass();
const new();
const MyClass.name();
const new name();
factory MyClass() => ...
factory() => ...
factory MyClass.name() => ...
factory name() => ...

3. Semantics & Scoping Rules

3. 语义与作用域规则

3.1 Primary Initializer Scope

3.1 Primary初始化作用域

When a primary constructor is declared, its formal parameters are introduced into the Primary Initializer Scope. This scope is the current scope for non-late field initializers in the class body and the primary constructor's initializer list (after
this :
). This allows non-late fields to reference constructor parameters directly during declaration:
dart
class DeltaPoint(final int x, int delta) {
  // 'x' and 'delta' are in scope here
  final int y = x + delta;
}
当声明Primary Constructor时,其形参被引入Primary Initializer Scope。该作用域是类体中非late字段初始化器以及Primary Constructor初始化列表(
this :
之后)的当前作用域。 这允许非late字段在声明期间直接引用构造函数参数:
dart
class DeltaPoint(final int x, int delta) {
  // 'x' and 'delta' are in scope here
  final int y = x + delta;
}

3.2 Late Instance Variables Restriction

3.2 Late实例变量限制

The primary initializer scope is not active for
late
instance variable initializers.
  • Since
    late
    variables can be evaluated after construction has completed, their initializers cannot safely access constructor parameters.
  • Attempting to access a primary constructor parameter in a
    late
    field initializer results in a compile-time error.
Primary初始化作用域对
late
实例变量初始化器生效。
  • 由于
    late
    变量可在构造完成后求值,其初始化器无法安全访问构造函数参数。
  • 尝试在
    late
    字段初始化器中访问Primary Constructor参数会导致编译时错误。

3.3 Shadowing

3.3 遮蔽

Primary constructor parameters shadow class members (fields) of the same name within the primary initializer scope:
  • In a non-late initializer:
    int y = x
    refers to parameter
    x
    .
  • In a
    late
    initializer:
    late int y = x
    refers to field
    x
    (if it exists) because the parameter
    x
    is out of scope.
在Primary初始化作用域内,Primary Constructor参数会遮蔽同名的类成员(字段):
  • 在非late初始化器中:
    int y = x
    引用的是参数
    x
  • late
    初始化器中:
    late int y = x
    引用的是字段
    x
    (如果存在),因为参数
    x
    超出了作用域。

3.4 Generative Constructor Restrictions

3.4 生成构造函数限制

To guarantee that the primary constructor (and the associated initializer scope) always executes:
  • A class, mixin class, or enum declaration with a primary constructor cannot declare any other non-redirecting generative constructors (except extension types).
  • All other generative constructors declared in the body must redirect (directly or indirectly) to the primary constructor.
为确保Primary Constructor(及关联的初始化作用域)始终执行:
  • 带有Primary Constructor的类、mixin类或枚举声明不能声明任何其他非重定向生成构造函数(扩展类型除外)。
  • 体中声明的所有其他生成构造函数必须(直接或间接)重定向到Primary Constructor。

3.5 Parameter Mutation Errors

3.5 参数修改错误

Primary constructor parameters are non-assignable inside the initialization phase.
  • Any assignment to a parameter (e.g.,
    p = value
    ,
    p++
    ) inside field initializers or the
    this :
    initializer list is a compile-time error.
在初始化阶段,Primary Constructor参数不可赋值。
  • 在字段初始化器或
    this :
    初始化列表中对参数进行任何赋值操作(例如
    p = value
    p++
    )都会导致编译时错误。

3.6 Double Initialization Errors

3.6 重复初始化错误

Initializing a field twice (e.g., once in the field declaration/initializer and once in the
this :
initializer list or as an initializing formal) is a compile-time error.

对字段进行两次初始化(例如一次在字段声明/初始化器中,一次在
this :
初始化列表或作为初始化式形参)会导致编译时错误。

4. Diagnostics & Troubleshooting

4. 诊断与故障排除

Most errors and lints have quick-fixes, run
dart fix
to fix those violations. For other common errors, fix them using the following table:
Error / Lint CodeCommon CauseResolution
Invalid Late AccessReferencing a primary constructor parameter inside a
late
field initializer.
Make the field non-late, or pass the value through another non-late field.
fieldInitializedInInitializerAndDeclaration
Initializing a variable both in its declaration and in the
this :
list.
Remove one of the initializations.
nonRedirectingGenerativeConstructorWithPrimary
Declaring a in-body generative constructor in the body without redirecting to the primary.Change the in-body constructor such that it is redirecting (e.g.
this(...)
) or remove the in-body constructor.

大多数错误和提示都有快速修复方案,运行
dart fix
即可修复这些问题。对于其他常见错误,可使用下表进行修复:
错误/提示代码常见原因解决方法
Invalid Late Access
late
字段初始化器中引用Primary Constructor参数。
将字段设为非late,或通过另一个非late字段传递值。
fieldInitializedInInitializerAndDeclaration
同时在声明和
this :
列表中初始化变量。
删除其中一个初始化操作。
nonRedirectingGenerativeConstructorWithPrimary
在体中声明了非重定向到Primary Constructor的体内生成构造函数。修改体内构造函数使其重定向(例如
this(...)
),或删除该体内构造函数。

5. Step-by-Step Refactoring Workflows

5. 分步重构流程

Workflow 5.1: Migrating a Class to a Primary Constructor

流程5.1:将类迁移至Primary Constructor

Follow these steps to migrate a verbose class to the new primary constructor syntax:
  1. Identify Candidate Fields and Constructor: Locate generative constructors and the fields they initialize. In this case, this would be the
    name
    and
    age
    fields.
    dart
    // Before
    class User {
      final String name;
      final int age;
      User(this.name, this.age);
    }
  2. Move Fields to the Header: Place fields in the header with
    final
    or
    var
    modifiers and append a semicolon (
    ;
    ) if the body is empty. The
    name
    and
    age
    fields are now written the primary constructor as declaring parameters
    final String name
    and
    final int age
    , respectively.
    dart
    // After
    class User(final String name, final int age);
  3. Handle Custom Initializers and Assertions: If there is an initializer list or assert block, move it to a
    this
    block inside the body:
    dart
    // Before
    class Point {
      final int x;
      final int y;
      Point(this.x, this.y) : assert(x >= 0);
    }
    
    // After
    class Point(final int x, final int y) {
      this : assert(x >= 0);
    }
  4. Leverage Primary Initializer Scope for Calculations: If a field value is calculated from parameters, declare it inside the body and assign it directly using the parameters:
    dart
    // Before
    class Rect {
      final double width;
      final double height;
      final double area;
      Rect(this.width, this.height) : area = width * height;
    }
    
    // After
    class Rect(final double width, final double height) {
      // 'width' and 'height' are in scope here
      final double area = width * height;
    }
  5. Convert In-Body Constructors to Redirecting: Ensure all in-body generative constructors redirect to the primary constructor:
    dart
    // Before
    class Point {
      final int x;
      final int y;
      Point(this.x, this.y);
      Point.zero() : x = 0, y = 0;
    }
    
    // After
    class Point(final int x, final int y) {
      new zero() : this(0, 0); // Redirects to primary
    }
按照以下步骤将冗长的类迁移至新的Primary Constructor语法:
  1. 识别候选字段和构造函数: 定位生成构造函数及其初始化的字段。在本例中,候选字段是
    name
    age
    dart
    // Before
    class User {
      final String name;
      final int age;
      User(this.name, this.age);
    }
  2. 将字段移至类头: 将字段添加到类头中,使用
    final
    var
    修饰符,如果体为空则追加分号(
    ;
    )。
    name
    age
    字段现在作为声明式参数
    final String name
    final int age
    写入Primary Constructor。
    dart
    // After
    class User(final String name, final int age);
  3. 处理自定义初始化器和断言: 如果存在初始化列表或断言块,将其移至体中的
    this
    块内:
    dart
    // Before
    class Point {
      final int x;
      final int y;
      Point(this.x, this.y) : assert(x >= 0);
    }
    
    // After
    class Point(final int x, final int y) {
      this : assert(x >= 0);
    }
  4. 利用Primary初始化作用域进行计算: 如果字段值由参数计算得出,在体中声明该字段并直接使用参数赋值:
    dart
    // Before
    class Rect {
      final double width;
      final double height;
      final double area;
      Rect(this.width, this.height) : area = width * height;
    }
    
    // After
    class Rect(final double width, final double height) {
      // 'width' and 'height' are in scope here
      final double area = width * height;
    }
  5. 将体内构造函数转换为重定向构造函数: 确保所有体内生成构造函数都重定向到Primary Constructor:
    dart
    // Before
    class Point {
      final int x;
      final int y;
      Point(this.x, this.y);
      Point.zero() : x = 0, y = 0;
    }
    
    // After
    class Point(final int x, final int y) {
      new zero() : this(0, 0); // Redirects to primary
    }

Workflow 5.2: Applying Abbreviated (Concise) In-Body Constructors

流程5.2:应用简洁缩写体内构造函数

When the user prefers to keep the constructor in the class body but wants to reduce verbosity, suggest the abbreviated constructor syntax:
dart
// Before
class DatabaseService {
  final String url;
  DatabaseService(this.url);
  DatabaseService.local() : url = 'localhost';
  factory DatabaseService.create() => DatabaseService('default');
}

// After
class DatabaseService {
  final String url;
  new(this.url); // Omit class name, use 'new'
  new local() : url = 'localhost'; // Use 'new local' for named constructors
  factory create() => DatabaseService('default'); // Omit class name from factory
}
当用户希望将构造函数保留在类体中但想减少冗余时,建议使用缩写构造函数语法:
dart
// Before
class DatabaseService {
  final String url;
  DatabaseService(this.url);
  DatabaseService.local() : url = 'localhost';
  factory DatabaseService.create() => DatabaseService('default');
}

// After
class DatabaseService {
  final String url;
  new(this.url); // Omit class name, use 'new'
  new local() : url = 'localhost'; // Use 'new local' for named constructors
  factory create() => DatabaseService('default'); // Omit class name from factory
}