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.

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

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);
要声明主构造函数,需在类型名称(及可选类型参数)后直接添加参数列表:
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.
主构造函数参数列表区分三种参数类型:
  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 常量主构造函数

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);
}
要将主构造函数设为
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 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
    .
扩展类型必须使用主构造函数。
  • 头部中的单个参数是表示字段。
  • 表示变量不能使用
    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 主构造函数的体内部分(
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 {...}
).
如果主构造函数需要断言或自定义字段初始化,可在体中使用
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 主初始化作用域

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;
}
当声明主构造函数时,其形参被引入主初始化作用域。此作用域是类体中非延迟字段初始化器和主构造函数初始化列表(
this :
之后)的当前作用域。 这允许非延迟字段在声明期间直接引用构造函数参数:
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 延迟实例变量限制

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.
主初始化作用域对
late
实例变量初始化器生效。
  • 由于
    late
    变量可在构造完成后求值,其初始化器无法安全访问构造函数参数。
  • 尝试在
    late
    字段初始化器中访问主构造函数参数会导致编译时错误。

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.
在主初始化作用域内,主构造函数参数会遮蔽同名的类成员(字段):
  • 在非延迟初始化器中:
    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.
为保证主构造函数(及关联的初始化作用域)始终执行:
  • 带有主构造函数的类、mixin类或枚举声明不能声明任何其他非重定向生成构造函数(扩展类型除外)。
  • 体内声明的所有其他生成构造函数必须(直接或间接)重定向到主构造函数。

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.
在初始化阶段,主构造函数参数不可赋值。
  • 在字段初始化器或
    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
字段初始化器中引用主构造函数参数。
将字段改为非延迟,或通过另一个非延迟字段传递值。
fieldInitializedInInitializerAndDeclaration
在字段声明和
this :
列表中都初始化了同一个变量。
删除其中一个初始化操作。
nonRedirectingGenerativeConstructorWithPrimary
在体内声明了非重定向的生成构造函数,未重定向到主构造函数。修改体内构造函数使其重定向(例如
this(...)
),或删除该体内构造函数。

5. Step-by-Step Refactoring Workflows

5. 分步重构流程

Workflow 5.1: Migrating a Class to a Primary Constructor

流程5.1:将类迁移到主构造函数

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
    }
按照以下步骤将冗长的类迁移到新的主构造函数语法:
  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
    写入主构造函数。
    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. 利用主初始化作用域进行计算: 如果字段值由参数计算得出,在体内声明该字段并直接使用参数赋值:
    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. 将体内构造函数转换为重定向构造函数: 确保所有体内生成构造函数都重定向到主构造函数:
    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
}