DART 基础拾遗系列之模式匹配
Matching 匹配
模式总是针对值进行测试,以确定该值是否具有您所期望的形式。
许多模式都使用子模式,有时分别称为外模式和内模式。模式在其子模式上进行递归匹配。例如,任何集合类型模式的各个字段都可以是变量模式或常量模式:
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');
}
Destructuring 解构
当对象和模式匹配时,模式就可以访问对象的数据,并将其部分提取出来。换句话说,模式会解构对象并赋值:
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); // 6
你可以在解构模式中嵌套任何类型的模式。例如,这种情况模式匹配并解构一个具有两个元素的列表,其中第一个元素为 "a "或 "b "的:
var list = ['a', 2];
switch (list) {
case ['a' || 'b', var c]:
print(c); // 2
}
var list = ['s', 2];
switch (list) {
case ['a' || 'b', var c]:
print(c);
} // 模式不匹配,没有输出
Places patterns can appear 可能出现模式匹配的地方
- Local variable declarations and assignments 局部变量声明和赋值
- for 和 for-in loops
- if-case和 switch-case
- 集合迭代控制流中
Variable declaration 变更声明
// Declares new variables a, b, and c.
var (a, [b, c]) = ('str', [1, 2]);
Variable assignment 变量赋值
var (a, b) = ('left', 'right');
(b, a) = (a, b); // Swap.
print('$a $b'); // Prints "right left".
Switch statements and expressions Switch 语名和表达式
解构的值为局部变量。它们的作用域仅限于该案例的主体。
var obj = (1, 2);
switch (obj) {
// Matches if 1 == obj.
case 1:
print('one');
// 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'); // a = 1, b = 2
default:
}
var obj = (1, 2);
switch (obj) {
// Matches if 1 == obj.
case 1:
print('one');
// Matches if the value of obj is between the
// constant values of 1 and 10.
case >= 1 && <= 10:
print('in range'); // in range
逻辑或 在多个case 条件符合共用一个 case 主体是通常比较有用:
switch (shape) {
case Square(size: var s) || Circle(size: var s) when s > 0:
print('Non-empty symmetric shape');
}
For and for-in 循环
可以在 for 循环和 for-in 循环中使用模式匹配来遍历和解构集合中的值。
var hist = <String, int>{
'a': 23,
'b': 100,
};
for (var MapEntry(key: key, value: count) in hist.entries) {
print('$key occurred $count times');
}
// a occurred 23 times
// b occurred 100 times
因为解构使用的键(getter)和解构后的变量名相同,还可以这样简写:
var hist = <String, int>{
'a': 23,
'b': 100,
};
for (var MapEntry(:key, :value) in hist.entries) {
print('$key occurred $value times');
}
解构多个返回值
Records 允许从单个函数调用中汇总和返回多个值。模式匹配使记返回记录字段可直接解构为本地变量中的能力,相当于解构后赋值。
// before
var info = userInfo(json);
var name = info.$1;
var age = info.$2;
//after
var (name, age) = userInfo(json);
解构类实例
对象的模式匹配允许你使用类中已公开的getter解构实类中的数据:
void main(List<String> args) {
final Foo myFoo = Foo(one: 'one', two: 2);
var Foo(:one, :two) = myFoo;
print('one $one, two $two');
}
class Foo {
final one;
final two;
Foo({required this.one, required this.two}); // one one, two 2
}
Algebraic data types
sealed class Shape {}
class Square implements Shape {
final double length;
Square(this.length);
}
class Circle implements Shape {
final double radius;
Circle(this.radius);
}
// Shape 的所有子类必须有一个switch 不然编译前的语法分析过不了
double calculateArea(Shape shape) => switch (shape) {
Square(length: var l) => l * l,
Circle(radius: var r) => math.pi * r * r
};
```
### 验证传入的JSON
```dart
// before
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.');
}
}
// after 使用 if-case
if (json case {'user': [String name, int age]}) {
print('User $name is $age years old.');
}
JSON 的解构赋值
var user = {
'user': ['lily', 12]
};
var {'user': [name as String, age as num]} = user;
print('$name $age'); // lily 12
Pattern Types 模式种类
模式的优先级
- 逻辑或< 逻辑与 < 关系运算(==, !=, <, >, <=, and >=.)
- 后置(cast, null-check, and null-assert) 优先级相同
- 其余主要模式的优先级最高。集合类型(Records、List和Map)和对象模式作为外层模式首先进行评估。
以上优先级依次升高,当然,可以使用括号来调整优先级。
逻辑或:
subpattern1 || subpattern2
var isPrimary = switch (color) {
Color.red || Color.yellow || Color.blue => true,
_ => false
};
逻辑与:
subpattern1 && subpattern2
switch ((1, 2)) {
// Error, both subpatterns attempt to bind 'b'.
// The variable 'b' is already defined in this pattern. Try renaming the variable.
case (var a, var b) && (var b, var c):
print('$a$b$c');
// ...
}
}
// 如下可正常运行
switch ((1, 2)) {
case (var a, var _) && (var _, var c):
print('$a$c'); // 12
// 使用case 表达式
var f = switch ((1, 2)) {
(var a, var b) && (var k, var c) => '$a $b $k $c',
};
print(f); // 1 2 1 2
关系型运算
== expression
< expression
String asciiCharType(int char) {
const space = 32;
const zero = 48;
const nine = 57;
return switch (char) {
< space => 'control',
== space => 'space',
> space && < zero => 'punctuation',
>= zero && <= nine => 'digit',
_ => 'fffffffff' };
}
print(asciiCharType(18)); // control
print(asciiCharType(32)); // space
print(asciiCharType(1132)); // fffffffff
Cast
foo as String
(num, Object) record = (1, 's');
var (i as int, s as String) = record;
Null-check
String? maybeString = 'nullable with base type String';
switch (maybeString) {
case var s:
print(s); // nullable with base type String
}
// -----------
String? maybeString;
switch (maybeString) {
case var s:
print(s); // null
}
// --------
String? maybeString;
switch (maybeString) {
case var s?:
print(1); // nothing stdout
}
Null-assert
List<String?> row = ['user', null];
switch (row) {
case ['user', var name!]:
// Unhandled exception:
// Null check operator used on a null value
}
List<String?> row = ['user', 'cc'];
switch (row) {
case ['user', var name!]:
print(name); // cc
}
List<String?> row = ['user', null];
switch (row) {
case ['user', var name]:
print(name); // null
}
// 非空断言
(int?, int?) position = (2, 3);
var (x!, y!) = position;
Constant 常量匹配
123, null, 'string', math.pi, SomeClass.constant, const Thing(1, 2), const (1 + 2)
Variable 变量
var bar, String str, final int _
Identifier 标识符, 同变量
foo, _
const c = 1;
switch (2) {
case c:
print('match $c');
default:
print('no match'); // Prints "no match".
}
switch ((1, 2)) {
case (var a, var b):
print('$a $b'); // 1 2
}
switch ((1, 2, 3)) {
case (var a, var b):
print('$a $b'); // 1 2
}
switch (1) {
case (var a, var b):
print('$a $b'); // 1 2
default:
print('none'); // Prints "none".
}
List 列表匹配
[subpattern1, subpattern2]
const a = 'a';
const b = 'b';
switch (['a', 'b']) {
// 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'); // Prints a, b
}
Rest element
var [a, b, ..., c, d] = [1, 2, 3, 4, 5, 6, 7];
// Prints "1 2 6 7".
print('$a $b $c $d');
var [a, b, ...r, c, d] = [1, 2, 3, 4, 5, 6, 7];
// Prints "1 2 6 7 [3, 4, 5]".
print('$a $b $c $d $r');
Record
(subpattern1, subpattern2)
(x: subpattern1, y: subpattern2)
var (myString: foo, myNumber: bar) = (myString: 'string', myNumber: 1);
print('$foo, $bar'); // string, 1
var record = (untyped: 'unknown', typed: 2);
var (untyped: untyped, typed: int typed) = record;
print('$untyped, $typed'); // unknown, 2
// 可简写
var record = (untyped: 'unknown', typed: 2);
var (:untyped, :int typed) = record;
print('$untyped, $typed'); // unknown, 2