跳到主要内容

术语表

以下是 Dart 文档中使用的术语的定义。

辅助

一种自动化的本地代码编辑,旨在对代码进行常见的改进。

辅助是一种自动化的本地代码编辑,旨在对代码进行常见的改进。辅助的示例包括将 switch 语句转换为 switch 表达式,反转 if 语句中的 thenelse 代码块,以及将 Widget 插入 Widget 结构中。

相关文档和资源

常量上下文

代码区域,其中 const 关键字是隐含的,并且该区域内的所有内容都必须是常量。

常量上下文是指代码区域,在该区域中,无需包含 const 关键字,因为它是隐含的,因为该区域中的所有内容都必须是常量。以下位置是常量上下文:

  • const 关键字为前缀的列表、Map 或 Set 字面量中的所有内容。例如:

    dart
    var l = const [/*constant context*/];
  • 常量构造函数调用的参数。例如:

    dart
    var p = const Point(/*constant context*/);
  • const 关键字为前缀的变量的初始化器。例如:

    dart
    const v = /*constant context*/;
  • 注解。

  • case 子句中的表达式。例如:

    dart
    void f(int e) {
      switch (e) {
        case /*constant context*/:
          break;
      }
    }

确定赋值

确定变量在使用前是否已被明确赋值。

确定赋值分析是确定对于代码中每个点的每个局部变量,以下哪项为真的过程:

  • 变量已被明确赋值(确定赋值)。
  • 变量已被明确未赋值(确定未赋值)。
  • 变量可能已被赋值,也可能未被赋值,具体取决于到达该点的执行路径。

确定赋值分析有助于查找代码中的问题,例如可能未赋值的变量被引用的位置,或者只能赋值一次的变量在可能已被赋值后再次赋值的位置。

例如,在以下代码中,当变量 s 作为参数传递给 print 时,它肯定是未赋值的:

dart
void f() {
  String s;
  print(s);
}

但在以下代码中,变量 s 肯定是已赋值的:

dart
void f(String name) {
  String s = 'Hello $name!';
  print(s);
}

当存在多个可能的执行路径时,确定赋值分析甚至可以判断变量是否肯定已赋值(或未赋值)。在以下代码中,如果执行通过 if 语句的 true 或 false 分支,则会调用 print 函数,但由于无论采用哪个分支都对 s 进行了赋值,因此在将其传递给 print 之前,它肯定是已赋值的:

dart
void f(String name, bool casual) {
  String s;
  if (casual) {
    s = 'Hi $name!';
  } else {
    s = 'Hello $name!';
  }
  print(s);
}

在流分析中,if 语句的末尾被称为汇合点——两个或多个执行路径在此处合并在一起。在存在汇合点的情况下,分析表明,如果变量在所有合并的路径上都被确定赋值,则该变量被确定赋值;如果变量在所有路径上都被确定未赋值,则该变量被确定未赋值。

有时,变量在一个路径上被赋值,但在另一个路径上未被赋值,在这种情况下,变量可能已被赋值,也可能未被赋值。在以下示例中,if 语句的 true 分支可能执行也可能不执行,因此变量可能已被赋值,也可能未被赋值:

dart
void f(String name, bool casual) {
  String s;
  if (casual) {
    s = 'Hi $name!';
  }
  print(s);
}

如果存在一个不为 s 赋值的 false 分支,情况也是如此。

对循环的分析稍微复杂一些,但它遵循相同的基本原理。例如,while 循环中的条件始终会执行,但循环体可能执行也可能不执行。因此,就像 if 语句一样,在 while 语句的末尾,条件为 true 的路径和条件为 false 的路径之间存在一个汇合点。

函数

用于指代顶层函数、局部函数、静态方法和实例方法的通用术语。

不可反驳的模式

总是匹配的模式。

不可反驳的模式是总是匹配的模式。不可反驳的模式是唯一可以出现在不可反驳上下文中的模式:声明赋值模式上下文。

混入应用

当混入应用于类时创建的类。

混入应用是指当混入应用于类时创建的类。例如,考虑以下声明:

dart
class A {}

mixin M {}

class B extends A with M {}

BM 混入 A 的混入应用的子类,有时命名为 A+M。类 A+MA 的子类,并具有从 M 复制的成员。

你可以通过将其定义为混入应用来为其指定实际名称:

dart
class A {}

mixin M {}

class A_M = A with M;

给定 A_M 的声明,以下 B 的声明等效于原始示例中 B 的声明:

dart
class B extends A_M {}

相关文档和资源

覆盖推断

方法声明中缺失的类型如何被推断。

覆盖推断是指根据其覆盖的方法或方法中相应的类型来推断方法声明中任何缺失类型的过程。

如果候选方法(缺少类型信息的方法)覆盖了单个继承的方法,则会推断出被覆盖方法中的相应类型。例如,考虑以下代码:

dart
class A {
  int m(String s) => 0;
}

class B extends A {
  @override
  m(s) => 1;
}

Bm 的声明是一个候选方法,因为它缺少返回类型和参数类型。由于它覆盖了单个方法(A 中的方法 m),因此将使用被覆盖方法中的类型来推断缺失的类型,并且它将等同于 B 中的方法被声明为 int m(String s) => 1;

如果候选方法覆盖了多个方法,并且这些被覆盖方法之一 Ms 的函数类型是所有其他被覆盖方法的函数类型的超类型,则使用 Ms 来推断缺失的类型。例如,考虑以下代码:

dart
class A {
  int m(num n) => 0;
}

class B {
  num m(int i) => 0;
}

class C implements A, B {
  @override
  m(n) => 1;
}

Cm 的声明是覆盖推断的候选方法,因为它缺少返回类型和参数类型。它覆盖了 A 中的 mB 中的 m,因此编译器需要选择其中一个来推断缺失的类型。但是,由于 Am 的函数类型(int Function(num))是 Bm 的函数类型(num Function(int))的超类型,因此使用 A 中的函数来推断缺失的类型。结果与将 C 中的方法声明为 int m(num n) => 1; 相同。

如果所有被覆盖的方法中都没有一个函数类型是所有其他被覆盖方法的函数类型的超类型,则会发生错误。

相关文档和资源

Part 文件

包含 part of 指令的 Dart 源代码文件。

Part 文件是包含 part of 指令的 Dart 源代码文件,并使用 part 指令包含在库中。

潜在非空类型

显式非空类型或由于是类型参数而成为非空类型的类型。

如果类型是显式非空类型,或者它是类型参数,则该类型是潜在非空类型

如果类型名称后没有问号(?),则该类型是显式非空类型。请注意,有一些类型始终可为空,例如 Nulldynamic,并且只有当 FutureOr 后没有问号并且类型参数是非空类型时(例如 FutureOr<String>),FutureOr 才是非空类型。

类型参数是潜在非空类型,因为实际运行时类型(指定为类型参数的类型)可能非空。例如,给定 class C<T> {} 的声明,类型 C 可以与非空类型参数一起使用,如 C<int> 中所示。

相关文档和资源

公共库

位于包的 lib 目录中,但不位于 lib/src 目录内的库。

公共库是位于包的 lib 目录中,但不位于 lib/src 目录内的库。

快速修复

一种自动化的本地代码编辑,旨在修复特定诊断报告的问题。

重构

一种代码编辑,旨在进行非本地或需要用户交互的修改。

重构是一种代码编辑,旨在进行非本地或需要用户交互的修改。重构的示例包括重命名、删除或提取代码。

相关文档和资源

可反驳的模式

可以针对值进行测试的模式。

可反驳的模式是可以针对值进行测试以确定模式是否与值匹配的模式。如果不匹配,则模式反驳或拒绝匹配。可反驳的模式出现在匹配上下文中。

子类

继承另一个类的实现的类。

子类是通过使用 extends 关键字或通过 混入应用 来继承另一个类的实现的类。

dart
// A is a subclass of B; B is the superclass of A.
class A extends B {}

// B1 has the superclass `A with M`, which has the superclass A.
class B1 extends A with M {}

子类关系也暗示着相关的子类型关系。例如,class A 隐式定义了一个关联类型 A,类 A 的实例属于该类型。因此,class A extends B 不仅声明类 AB 的子类,还确立了类型 A 是类型 B子类型

子类关系是子类型关系的子集。当文档说“S 必须是 T 的子类型”时,ST 的子类是可以的。但是,反之则不然:并非所有子类型都是子类。

子类型

可以在期望其超类型值的地方使用的类型。

子类型关系是指某种类型的值可以替代另一种类型(超类型)的值。例如,如果 ST 的子类型,那么你可以在期望类型 T 的值的地方替换类型 S 的值。

子类型支持其超类型的所有操作(以及可能的一些额外操作)。实际上,这意味着你可以将子类型的值分配给任何期望超类型的位置,并且超类型的所有方法都可在子类型上使用。

这至少在静态上是正确的。特定的 API 可能不允许在运行时进行替换,具体取决于其操作。

一些子类型关系基于类型的结构,例如可空类型(例如,intint? 的子类型)和函数类型(例如,String Function()void Function() 的子类型)。

子类型也可以通过实现继承(直接或间接)为类引入。

dart
// A is a subtype of B, but NOT a subclass of B.
class A implements B {}

// C is a subtype AND a subclass of D.
class C extends D {}

变体和变体位置

更改类型的类型参数如何影响原始类型和结果类型之间的关系。

在 Dart 中,更改类型声明(如类)或函数返回类型的类型参数,会以相同的方向(协变)更改整体类型关系。

但是,更改函数参数类型的类型,会以相反的方向(逆变)更改整体类型关系。

当类型作为一个整体“共同变化”实际类型参数时,类的类型参数(或其他类型声明,如混入)被称为协变。换句话说,如果类型参数被子类型替换,则类型作为一个整体也是子类型。

例如,类 List 的类型参数是协变的,因为列表类型与其类型参数共同变化:List<int>List<Object> 的子类型,因为 intObject 的子类型。

在 Dart 中,所有类、混入、混入类和枚举声明的所有类型参数都是协变的。

但是,函数类型是不同的:函数类型在其返回类型中是协变的,但在其参数类型中是相反的(称为逆变)。例如,类型 int Function(int) 是类型 Object Function(int) 的子类型,但它是 int Function(Object) 的超类型。

如果你考虑它们的可替换性,这将很有意义。如果你使用 int Function(int) 的静态类型调用函数,则该函数在运行时实际上可以是 int Function(Object) 类型。根据静态类型,你希望能够将 int 传递给它。这将没问题,因为该函数实际上接受任何 Object,其中包括 int 类型的每个对象。同样,返回的结果将是 int 类型,这也是你根据静态类型所期望的。

因此,int Function(Object)int Function(int) 的子类型。

请注意,对于参数类型,一切都颠倒了。特别是,函数类型之间的这种子类型关系要求参数类型存在相反的子类型关系。例如,void Function(Object)void Function(int) 的子类型,因为 intObject 的子类型。

对于像 List<void Function(int)> 这样更复杂的类型,你必须考虑类型中的位置。要完成此操作,请将类型的一部分转换为占位符,然后考虑当不同类型放置在该位置时类型会发生什么变化。

例如,考虑 List<void Function(_)> 作为类型的模板,你可以在占位符 _ 的位置放置不同的类型。此类型在占位符出现的位置是逆变的。

以下通过将 Objectint 替换为 _ 来对此进行说明。List<void Function(Object)>List<void Function(int)> 的子类型,因为 void Function(Object)void Function(int) 的子类型,因为 voidvoid 的子类型(返回类型),并且 intObject 的子类型(参数类型,顺序相反)。因此,_ 处的类型与类型 List<void Function(_)> 作为一个整体的方向相反地变化,并且这种“相反方向”根据定义使其成为逆变位置

协变位置的定义类似。例如,_ 在类型 List<_> 中处于协变位置,并且 _ 在类型 _ Function(int) 中也处于协变位置。

还有另一种位置称为不变,但它很少出现,因此此处省略了详细信息。

在实践中,通常只需要知道类、混入等的类型参数处于协变位置,函数类型的返回类型也是如此,但参数类型处于逆变位置。