目录

模式

:::版本说明 模式至少需要 3.0 的 语言版本 。 :::

模式是 Dart 语言中的句法类别,类似于语句和表达式。模式表示它可能与实际值匹配的一组值的形状。

此页面描述:

  • 模式的作用。
  • Dart 代码中允许使用模式的位置。
  • 模式的常见用例。

要了解不同类型的模式,请访问 模式类型 页面。

模式的作用

#

通常,模式可以根据上下文和模式的形状来 匹配 值、 解构 值或同时进行两者。

首先, 模式匹配 允许您检查给定值是否:

  • 具有某种形状。
  • 是某个常量。
  • 等于其他内容。
  • 具有某种类型。

然后, 模式解构 为您提供了一种方便的声明式语法,可以将该值分解为其组成部分。相同的模式还可以让您在此过程中将变量绑定到这些部分的全部或部分。

匹配

#

模式始终针对值进行测试,以确定该值是否具有您预期的形式。换句话说,您正在检查值是否 匹配 模式。

构成匹配的内容取决于您正在使用的 模式类型 。例如,如果值等于模式的常量,则常量模式匹配:

dart
switch (number) {
  // 如果 1 == number,则常量模式匹配。
  case 1:
    print('one');
}

许多模式都使用子模式,有时分别称为 外部 模式和 内部 模式。模式在其子模式上递归匹配。例如,任何 集合类型 模式的各个字段可以是 变量模式常量模式

dart
const a = 'a';
const b = 'b';
switch (obj) {
  // 如果 obj 是一个包含两个字段的列表,则列表模式 [a, b] 首先匹配 obj,然后如果其字段匹配常量子模式 'a' 和 'b'。
  case [a, b]:
    print('$a, $b');
}

要忽略匹配值的部分,可以使用 通配符模式 作为占位符。对于列表模式,可以使用 剩余元素

解构

#

当对象和模式匹配时,模式可以访问对象的 数据并将其分解成各个部分。换句话说,模式 解构 对象:

dart
var numList = [1, 2, 3];
// 列表模式 [a, b, c] 解构 numList 中的三个元素...
var [a, b, c] = numList;
// ...并将它们分配给新的变量。
print(a + b + c);

您可以在解构模式内嵌套 任何类型的模式 。例如,此 case 模式匹配并解构一个包含两个元素的列表,其第一个元素是 'a''b'

dart
switch (list) {
  case ['a' || 'b', var c]:
    print(c);
}

模式可以出现的位置

#

您可以在 Dart 语言的多个位置使用模式:

本节介绍使用模式进行匹配和解构的常见用例。

变量声明

#

您可以在 Dart 允许局部变量声明的任何位置使用 模式变量声明 。 模式与声明右侧的值匹配。匹配后,它会解构值并将其绑定到新的局部变量:

dart
// 声明新的变量 a、b 和 c。
var (a, [b, c]) = ('str', [1, 2]);

模式变量声明必须以 varfinal 开头,后跟一个模式。

变量赋值

#

变量赋值模式 位于赋值左侧。 首先,它解构匹配的对象。然后它将值分配给 现有 变量,而不是绑定新的变量。

使用变量赋值模式交换两个变量的值,无需声明第三个临时变量:

dart
var (a, b) = ('left', 'right');
(b, a) = (a, b); // 交换。
print('$a $b'); // 打印 "right left"。

switch 语句和表达式

#

每个 case 子句都包含一个模式。这适用于 switch 语句表达式 ,以及 if-case 语句 。您可以在 case 中使用 任何类型的模式

_Case 模式_是 可反驳的 。它们允许控制流执行以下任一操作:

  • 匹配并解构正在切换的对象。
  • 如果对象不匹配,则继续执行。

模式在 case 中解构的值成为局部变量。它们的范围仅限于该 case 的主体。

dart
switch (obj) {
  // 如果 1 == obj,则匹配。
  case 1:
    print('one');

  // 如果 obj 的值介于 'first' 和 'last' 的常量值之间,则匹配。
  case >= first && <= last:
    print('in range');

  // 如果 obj 是一个包含两个字段的记录,则匹配,然后将字段分配给 'a' 和 'b'。
  case (var a, var b):
    print('a = $a, b = $b');

  default:
}

逻辑或模式 对于让多个 case 共享 switch 表达式或语句中的主体很有用:

dart
var isPrimary = switch (color) {
  Color.red || Color.yellow || Color.blue => true,
  _ => false
};

switch 语句可以有多个 case 共享一个主体 无需使用逻辑或模式 ,但它们仍然非常有用,因为它们允许多个 case 共享一个 保护条件

dart
switch (shape) {
  case Square(size: var s) || Circle(size: var s) when s > 0:
    print('Non-empty symmetric shape');
}

保护条件 评估任意条件作为 case 的一部分,如果条件为假则不会退出 switch(就像在 case 主体中使用 if 语句一样)。

dart
switch (pair) {
  case (int a, int b):
    if (a > b) print('First element greater');
  // 如果为假,则不打印任何内容并退出 switch。
  case (int a, int b) when a > b:
    // 如果为假,则不打印任何内容,但继续进行下一个 case。
    print('First element greater');
  case (int a, int b):
    print('First element not greater');
}

For 和 for-in 循环

#

您可以在 for 和 for-in 循环 中使用模式来迭代和解构集合中的值。

此示例在 for-in 循环中使用 对象解构 来解构 <Map>.entries 调用返回的 MapEntry 对象:

dart
Map<String, int> hist = {
  'a': 23,
  'b': 100,
};

for (var MapEntry(key: key, value: count) in hist.entries) {
  print('$key occurred $count times');
}

对象模式检查 hist.entries 是否具有名为 MapEntry 的类型,然后递归到名为 keyvalue 的字段子模式。它在每次迭代中调用 MapEntry 上的 key getter 和 value getter,并将结果分别绑定到局部变量 keycount

将 getter 调用的结果绑定到同名变量是一种常见用例,因此对象模式还可以从 变量子模式 推断 getter 名称。这允许您将变量模式从类似 key: key 的冗余内容简化为仅 :key

dart
for (var MapEntry(:key, value: count) in hist.entries) {
  print('$key occurred $count times');
}

模式的用例

#

上一节 描述了模式 如何 融入其他 Dart 代码结构。您看到了一些有趣的用例作为示例,例如 交换 两个变量的值,或 解构映射中的键值对 。本节介绍更多用例,回答:

  • 何时以及为何 您可能想要使用模式。
  • 它们解决的问题。
  • 它们最适合的习惯用法。

解构多个返回值

#

记录允许汇总和 从单个函数调用返回多个值 。模式增加了直接将记录的字段解构到局部变量中的能力,与函数调用内联。

无需分别为每个记录字段声明新的局部变量,如下所示:

dart
var info = userInfo(json);
var name = info.$1;
var age = info.$2;

您可以使用 变量声明赋值模式 以及 记录模式 作为其子模式,将函数返回的记录的字段解构到局部变量中:

dart
var (name, age) = userInfo(json);

要使用模式解构具有命名字段的记录:

dart
final (:name, :age) =
    getData(); // 例如,返回 (name: 'doug', age: 25);

解构类实例

#

对象模式 与命名对象类型匹配,允许您使用对象类已公开的 getter 来解构其数据。

要解构类的实例,请使用命名类型,后跟括号中包含的要解构的属性:

dart
final Foo myFoo = Foo(one: 'one', two: 2);
var Foo(:one, :two) = myFoo;
print('one $one, two $two');

代数数据类型

#

对象解构和 switch case 有利于以 代数数据类型 样式编写代码。当出现以下情况时,使用此方法:

  • 您有一组相关的类型。
  • 您有一个需要针对每种类型进行特定行为的操作。
  • 您希望在一个地方分组该行为,而不是将其分散到所有不同的类型定义中。

无需为每种类型实现操作作为实例方法,请将操作的变化保留在一个切换子类型的函数中:

dart
sealed class Shape {}

class Square implements Shape {
  final double length;
  Square(this.length);
}

class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

double calculateArea(Shape shape) => switch (shape) {
      Square(length: var l) => l * l,
      Circle(radius: var r) => math.pi * r * r
    };

验证传入的 JSON

#

Map列表 模式非常适合解构 JSON 数据中的键值对:

dart
var json = {
  'user': ['Lily', 13]
};
var {'user': [name, age]} = json;

如果您知道 JSON 数据具有您期望的结构,则前面的示例是现实的。但数据通常来自外部来源,例如网络。您需要先验证它以确认其结构。

没有模式,验证会很冗长:

dart
if (json is Map<String, Object?> &&
    json.length == 1 &&
    json.containsKey('user')) {
  var user = json['user'];
  if (user is List<Object> &&
      user.length == 2 &&
      user[0] is String &&
      user[1] is int) {
    var name = user[0] as String;
    var age = user[1] as int;
    print('User $name is $age years old.');
  }
}

单个 case 模式 可以实现相同的验证。单个 case 最适合用作 if-case 语句。模式提供了一种更具声明性且更简洁的 JSON 验证方法:

dart
if (json case {'user': [String name, int age]}) {
  print('User $name is $age years old.');
}

此 case 模式同时验证:

  • json 是一个映射,因为它必须首先匹配外部 映射模式 才能继续。
    • 并且,由于它是一个映射,它也确认 json 不为 null。
  • json 包含一个键 user
  • user 与一个包含两个值的列表配对。
  • 列表值的类型为 Stringint
  • 用于保存值的新的局部变量为 nameage