模式

Last updated: ... / Reads: 43 Edit

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

模式是 Dart 语言中的一个句法类别,就像语句和表达式一样。 模式表示一组值的形状,它可以与实际值匹配 值。

本页介绍:

  • 模式的作用是什么。
  • 在 Dart 代码中允许模式的位置。
  • 模式的常见用例是什么。

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

模式的作用

通常,模式可以匹配值、解构值或两者兼而有之, 取决于图案的上下文和形状。

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

  • 具有一定的形状。
  • 是某个常数。
  • 等于其他东西。
  • 具有某种类型。

然后,模式解构为您提供了一种方便的声明式语法,用于 将该值分解为其组成部分。同样的图案也可以让你 将变量绑定到流程中的部分或全部部分。

匹配

模式始终针对值进行测试,以确定该值是否具有以下形式 你期望。换言之,您正在检查该值是否与模式匹配。

什么构成匹配取决于你使用的模式类型。 例如,如果常量模式的值等于模式的 不断:

switch (number) {
  // Constant pattern matches if 1 == number.
  case 1:
    print('one');
}

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

const a = 'a';
const b = 'b';
switch (obj) {
  // List pattern [a, b] matches obj first if obj is a list with two fields,
  // then if its fields match the constant subpatterns 'a' and 'b'.
  case [a, b]:
    print('$a, $b');
}

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

解构

当对象和模式匹配时,该模式可以访问对象的数据 并将其分批提取。换言之,模式对对象进行了解构:

var numList = [1, 2, 3];
// List pattern [a, b, c] destructures the three elements from numList...
var [a, b, c] = numList;
// ...and assigns them to new variables.
print(a + b + c);

您可以将任何类型的模式嵌套在解构模式中。 例如,此案例模式匹配并解构两个元素 列表,其第一个元素为 或 :'a''b'

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

可以出现的位置模式

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

  • 局部变量声明和赋值
  • for 和 for-in 循环
  • if-case 和 switch-case
  • 集合文本中的控制流

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

变量声明

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

// Declares new variables a, b, and c.
var (a, [b, c]) = ('str', [1, 2]);

模式变量声明必须以 either 或 开头,后跟 通过模式。varfinal

变量赋值

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

使用变量赋值模式交换两个变量的值,而不 宣布第三个临时的:

var (a, b) = ('left', 'right');
(b, a) = (a, b); // Swap.
print('$a $b'); // Prints "right left".

切换语句和表达式

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

案例模式是可以反驳的。 它们允许控制流:

  • 匹配并解构正在打开的对象。
  • 如果对象不匹配,请继续执行。

模式在案例中解构的值将成为局部变量。 其范围仅在该案件的正文范围内。

switch (obj) {
  // Matches if 1 == obj.
  case 1:
    print('one');

  // Matches if the value of obj is between the
  // constant values of 'first' and 'last'.
  case >= first && <= last:
    print('in range');

  // Matches if obj is a record with two fields,
  // then assigns the fields to 'a' and 'b'.
  case (var a, var b):
    print('a = $a, b = $b');

  default:
}

逻辑 or 模式对于让多个事例共享 switch 表达式或语句中的正文:

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

Switch 语句可以让多个事例共享一个主体,而无需使用逻辑或模式,但它们是 对于允许多个案例共享一个防护装置仍然特别有用:

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

保护条款将任意条件作为案例的一部分进行评估,而不 如果条件为 false,则退出交换机 (就像在案例正文中使用语句会导致)。if

switch (pair) {
  case (int a, int b):
    if (a > b) print('First element greater');
  // If false, prints nothing and exits the switch.
  case (int a, int b) when a > b:
    // If false, prints nothing but proceeds to next case.
    print('First element greater');
  case (int a, int b):
    print('First element not greater');
}

For 和 for-in 循环

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

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

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

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

对象模式检查具有命名类型 , 然后递归到命名字段子模式和 . 它在每次迭代中调用 getter 和 getter, 并将结果分别绑定到局部变量 和 。hist.entriesMapEntrykeyvaluekeyvalueMapEntrykeycount

将 getter 调用的结果绑定到同名变量是很常见的 用例,因此对象模式也可以从变量子模式中推断 getter 名称。这允许您简化变量模式 从冗余的东西到只是:key: key:key

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

模式的用例

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

  • 何时以及为何可能希望使用模式。
  • 他们解决了什么样的问题。
  • 他们最适合哪些成语。

解构多个返回

记录允许从单个函数聚合和返回多个值 叫。模式增加了对记录字段进行结构化的功能 直接进入局部变量,与函数调用内联。

而不是为每个记录字段单独声明新的局部变量, 喜欢这个:

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

您可以将函数返回的记录字段解构为本地 使用变量声明或同化模式的变量,以及记录模式作为其子模式的变量:

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

解构类实例

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

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

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

代数数据类型

对象解构和切换情况有利于写入 代数数据类型样式的代码。 在以下情况下使用此方法:

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

而不是将操作实现为每种类型的实例方法, 将操作的变体保留在切换子类型的单个函数中:

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

映射和列表模式非常适合解构 JSON数据:

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

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

如果没有模式,验证是冗长的:

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.');
  }
}

单个案例模式可以实现相同的验证。 单个案例作为 if-case 语句效果最好。 模式提供声明性更强、更不冗长 验证JSON的方法:

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

此案例模式同时验证:

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

Comments

Make a comment