内容

术语表

以下是整个 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 语句的真分支或假分支,则会调用 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 语句的真分支可能被执行,也可能不被执行,因此变量可能被分配了值,也可能没有被分配值

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

如果存在一个不向 s 分配值的假分支,则情况也是如此。

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

相关文档和资源

函数

#

一个总称,指代顶层函数、局部函数、静态方法和实例方法。

相关文档和资源

不可否认模式

#

不可否认模式是始终匹配的模式。不可否认模式是唯一可以出现在不可否认上下文中的模式:声明赋值 模式上下文。

相关文档和资源

Mixin 应用

#

Mixin 应用是指将 Mixin 应用于类时创建的类。例如,考虑以下声明

dart
class A {}

mixin M {}

class B extends A with M {}

B 是将 M 应用于 A 的 Mixin 应用的子类,有时也称为 A+M。类 A+MA 的子类,并具有从 M 复制的成员。

可以通过将其定义为以下方式为 Mixin 应用提供实际名称

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; 相同。

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

相关文档和资源

部分文件

#

部分文件是一个 Dart 源文件,它包含 part of 指令,并使用 part 指令包含在库中。

相关文档和资源

可能非空

#

如果类型是显式非空类型,或者是一个类型参数,则该类型为**潜在非空类型**。

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

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

相关文档和资源

公共库

#

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

相关文档和资源

快速修复

#

针对特定诊断报告的错误进行的自动化本地代码编辑。

相关文档和资源

重构

#

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

相关文档和资源

可否认模式

#

**可证伪模式**是一种可以针对值进行测试以确定模式是否与值匹配的模式。如果匹配失败,则模式会**证伪**或拒绝匹配。可证伪模式出现在匹配上下文中

相关文档和资源

子类

#

**子类**是一个类,它通过使用 extends 关键字或通过 mixin 应用 来继承另一个类的实现。

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 的子类型”时,S 可以是 T 的子类。但是,反之不成立:并非所有子类型都是子类。

相关文档和资源

子类型

#

**子类型**关系是指特定类型的值的替换位置,其中期望另一种类型(超类型)的值。例如,如果 ST 的子类型,那么您可以用类型 S 的值替换期望类型 T 的值。

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

这至少在静态上是正确的。具体 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 {}

相关文档和资源

方差和方差位置

#

类的类型参数(或其他类型声明,例如 mixin)被称为**协变**,当整个类型与实际类型参数“协变”时。换句话说,如果类型参数被子类型替换,则整个类型也是子类型。

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

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

但是,函数类型有所不同:函数类型在其返回值类型中是协变的,但在其参数类型中是相反的(称为**逆变**)。例如,类型 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(_)> 视为一个类型模板,您可以在其中将不同的类型替换为占位符 _。这种类型在占位符所在的 position 是逆变的。

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

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

还有另一种位置称为*不变*,但它很少见,因此这里省略了细节。

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

相关文档和资源