总算,总算,总算, Dart 支撑元组了! 官方称之为 Records 特性,所以入乡随俗,今后中文称之为 记载类型 。官方 对它的介绍 是:

Records are an anonymous, immutable, aggregate type.
记载是一种匿名的、不可变的聚合类型。


1. 记载类型的声明与拜访

经过 () 将若干个目标组合在一块,作为一个新的聚合类型。界说时能够直接当放入目标,也能够进行命名传入:

var record = ('first', a: 2, b: true, 'last');
print(record.runtimeType);
--->[打印输出]---
(String, String, {int a, bool b})

上面的 record 目标由四个数据构成,经过 runtimeType 能够查看到其运行时类型,类型为各个横死名数据类型 + 各命名类型。

横死名类型数据能够经过 $index 进行拜访:

print(record.$1);
print(record.$2);
--->[打印输出]---
first
last

命名类型数据能够经过 称号 进行拜访:

print(record.a);
print(record.b);
--->[打印输出]---
2
true

留意: 一个记载目标的数据不允许被修正:

Dart 3.0 语法新特性 |  Records 记录类型 (元组)


2. 记载类型声明目标

一个 Records 本质上也是一种类型,能够用该类型来声明目标,比方现在经过 (double,double,double) 的记载类型表明三个坐标,如下界说 p0 和 p1 目标:

void main() {
  (double x, double y, double z) p0 = (1, 2, 3);
  (double x, double y, double z) p1 = (1, 2, 6);
}

已然能够实例化为目标,那么自然也能够将其作为参数类型传入函数中,如下 distance 办法传入两个三维点,核算间隔:

double distance(
  (double x, double y, double z) p0,
  (double x, double y, double z) p1,
) {
  num result = pow(p0.$1 - p1.$1, 2) + pow(p0.$2 - p1.$2, 2) + pow(p0.$3 - p1.$3, 2);
  return sqrt(result);
}

但记载类型一旦显现声明,写起来比较繁琐;和函数类型类似,也能够经过 typedef 来界说类型的别号。如下所示,界说 Point3D 作为别号,功能是等价的,但书写和可读性会更好一些:

typedef Point3D = (double, double, double);
void main() {
  Point3D p0 = (1, 2, 3);
  Point3D p1 = (1, 2, 6);
  print(distance(p0, p1));
}
double distance(Point3D p0, Point3D p1) {
  num result = pow(p0.$1 - p1.$1, 2) + pow(p0.$2 - p1.$2, 2) + pow(p0.$3 - p1.$3, 2);
  return sqrt(result);
}

同理,记载类型也能够作为回来值,这样能够解决一个函数回来多值的问题。如下 addTask 办法能够核算 1 ~ count 的累加值,回来核算结果和耗时毫秒数:

({int result, int cost}) addTask2(int count) {
  int start = DateTime.now().millisecondsSinceEpoch;
  int sum = 0;
  for (int i = 0; i < count; i++) {
    sum += i;
  }
  int end = DateTime.now().millisecondsSinceEpoch;
  return (
    result: sum,
    cost: end - start,
  );
}

3. 记载类型目标的等值

记载类型会根据字段的结构主动界说 hashCode 和 == 办法。 所以两个记载目标持平,便是其中的各个数值持平。但是经过 identical 能够看出 p0 和 p1 仍是两个目标,内存地址不同:

(double, double, double) p0 = (1, 2, 3);
(double, double, double) p1 = (1, 2, 3);
print(p0 == p1);
print(identical(p0, p1));
--->[打印输出]---
true
false

如下所示,第二个数据是 List<double> 类型,两个 [2] 是两个不同的目标,所以 p2,p3 不持平:

(double, List<double>, double) p2 = (1, [2], 3);
(double, List<double>, double) p3 = (1, [2], 3);
print(p2==p3);
--->[打印输出]---
false

下面测试中, 列表运用同一目标,则 p2,p3 持平:

List<double> li = [2];
(double, List<double>, double) p2 = (1, li, 3);
(double, List<double>, double) p3 = (1, li, 3);
print(p2==p3);
--->[打印输出]---
true

4. 记载类型的价值

关于编程言语来说,Dart 的记载类型也不是什么新的东西,便是其他言语中的元组。如下所示,能够创建一个 TaskResult 类来维护数据作为回来值。但假如只是回来一些暂时的数据,为此新建一个类来维护数据就会显得比较繁琐,还要界说构造函数。

class TaskResult{
  final int result;
  final int cost;
  TaskResult(this.result, this.cost);
}
TaskResult addTask(int count) {
  int start = DateTime.now().millisecondsSinceEpoch;
  int sum = 0;
  for (int i = 0; i < count; i++) {
    sum += i;
  }
  int end = DateTime.now().millisecondsSinceEpoch;
  return TaskResult(sum, end - start);
}

除此之外,一个函数回来多个数据也能够运用 Map 目标:

Map<String,dynamic> addTask(int count) {
  int start = DateTime.now().millisecondsSinceEpoch;
  int sum = 0;
  for (int i = 0; i < count; i++) {
    sum += i;
  }
  int end = DateTime.now().millisecondsSinceEpoch;
  return {
    'result' : sum,
    'cost': end - start
  };
}

但这种方式的弊端也很明显,回来和运用时都需求固定的字符串作为 key。假如 key 写错了,代码在运行前也不会有任何过错,这样很容易呈现危险。多人协作时,而且假如函数的书写者和调用者不是一个人,那该运用什么键得到什么值就很难分辨。

Map<String,dynamic> task = addTask2(100000000);
print(task['result']);
print(task['cost']);

所以,相比于新建 class 或经过 Map 来维护多个数据,运用记载类型更加方便快捷和准确。但话说回来,假如特色数据量过多,运用记载类型看起来会非常麻烦,也不能界说成员办法来操作、修正内部数据。所以它有自己的特色运用场景,比方暂时聚合多个数据来方便运用。