内容

术语表

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

辅助

#

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

相关文档和资源

常量上下文

#

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

  • const 关键字为前缀的列表、映射或集合文字中的所有内容。 例如

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

BMA 的混入应用的子类,有时表示为 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;
}

B 中的 m 声明是一个候选声明,因为它缺少返回类型和参数类型。 因为它覆盖了单个方法(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;
}

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

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

相关文档和资源

Part 文件

#

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

相关文档和资源

潜在的不可为空

#

如果类型显式不可为空或类型参数,则该类型是潜在的不可为空

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

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

相关文档和资源

公共库

#

公共库是指位于包的 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 {}

相关文档和资源

变体和变体位置

#

当整个类型随着实际类型参数“协变”时,类(或其他类型声明,如混合)的类型参数被认为是协变的。换句话说,如果类型参数被子类型替换,那么整个类型也是一个子类型。

例如,类 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) 中也处于协变位置。

还有一种位置称为不变,但它发生的频率要低得多,因此此处省略了详细信息。

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

相关文档和资源