目录

扩展方法

扩展方法为现有库添加功能。您可能在不知不觉中使用了扩展方法。例如,当您在 IDE 中使用代码补全时,它会建议扩展方法以及常规方法。

如果观看视频能帮助您学习,请查看此扩展方法概述。


Dart extension methods

概述

#

当您使用他人的 API 或实现广泛使用的库时,更改 API 通常不切实际或不可能。但是您可能仍然希望添加一些功能。

例如,考虑以下将字符串解析为整数的代码:

dart
int.parse('42')

最好——更短且更易于与工具一起使用——让该功能在 String 上:

dart
'42'.parseInt()

要启用该代码,您可以导入包含 String 类扩展的库:

dart
import 'string_apis.dart';
// ···
print('42'.parseInt()); // 使用扩展方法。

扩展不仅可以定义方法,还可以定义其他成员,例如 getter、setter 和运算符。此外,扩展可以具有名称,如果出现 API 冲突,这将非常有用。以下是您可以使用在字符串上操作的扩展(名为 NumberParsing )实现扩展方法 parseInt() 的方法:

lib/string_apis.dart
dart
extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
  // ···
}

下一节描述如何 使用 扩展方法。之后是关于 实现 扩展方法的部分。

使用扩展方法

#

与所有 Dart 代码一样,扩展方法位于库中。您已经看到了如何使用扩展方法——只需导入它所在的库,并像使用普通方法一样使用它:

dart
// 导入包含 String 扩展的库。
import 'string_apis.dart';
// ···
print('42'.padLeft(5)); // 使用 String 方法。
print('42'.parseInt()); // 使用扩展方法。

这通常是您需要了解的关于使用扩展方法的所有内容。在编写代码时,您可能还需要了解扩展方法如何依赖于静态类型(而不是 dynamic )以及如何解决 API 冲突

静态类型和动态类型

#

您不能在类型为 dynamic 的变量上调用扩展方法。例如,以下代码会导致运行时异常:

dart
dynamic d = '2';
print(d.parseInt()); // 运行时异常:NoSuchMethodError

扩展方法 确实 适用于 Dart 的类型推断。以下代码是可以的,因为变量 v 推断为 String 类型:

dart
var v = '2';
print(v.parseInt()); // 输出:2

dynamic 不起作用的原因是扩展方法相对于接收器的静态类型解析。因为扩展方法是静态解析的,所以它们与调用静态函数一样快。

有关静态类型和 dynamic 的更多信息,请参阅 Dart 类型系统

API 冲突

#

如果扩展成员与接口或另一个扩展成员冲突,则您有几种选择。

一种选择是更改导入冲突扩展的方式,使用 showhide 来限制公开的 API:

dart
// 定义 String 扩展方法 parseInt()。
import 'string_apis.dart';

// 也定义 parseInt(),但隐藏 NumberParsing2
// 隐藏了该扩展方法。
import 'string_apis_2.dart' hide NumberParsing2;

// ···
// 使用 'string_apis.dart' 中定义的 parseInt()。
print('42'.parseInt());

另一种选择是显式应用扩展,这会导致代码看起来像扩展是一个包装器类:

dart
// 两个库都定义了包含 parseInt() 的 String 扩展,
// 并且扩展具有不同的名称。
import 'string_apis.dart'; // 包含 NumberParsing 扩展。
import 'string_apis_2.dart'; // 包含 NumberParsing2 扩展。

// ···
// print('42'.parseInt()); // 不起作用。
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());

如果两个扩展具有相同的名称,则您可能需要使用前缀进行导入:

dart
// 两个库都定义了名为 NumberParsing 的扩展
// 包含扩展方法 parseInt()。一个 NumberParsing
// 扩展(在 'string_apis_3.dart' 中)也定义了 parseNum()。
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;

// ···
// print('42'.parseInt()); // 不起作用。

// 使用 string_apis.dart 中的 ParseNumbers 扩展。
print(NumberParsing('42').parseInt());

// 使用 string_apis_3.dart 中的 ParseNumbers 扩展。
print(rad.NumberParsing('42').parseInt());

// 只有 string_apis_3.dart 具有 parseNum()。
print('42'.parseNum());

如示例所示,即使您使用前缀导入,您也可以隐式调用扩展方法。您唯一需要使用前缀的情况是当显式调用扩展时避免名称冲突。

实现扩展方法

#

使用以下语法创建扩展:

extension <extension name>? on <type> { // <extension-name> 可选
  (<member definition>)* // 可以提供一个或多个 <member definition>。
}

例如,以下是您可能如何在 String 类上实现扩展的方法:

lib/string_apis.dart
dart
extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }

  double parseDouble() {
    return double.parse(this);
  }
}

扩展的成员可以是方法、getter、setter 或运算符。扩展还可以具有静态字段和静态辅助方法。要访问扩展声明之外的静态成员,请像 类变量和方法 一样通过声明名称调用它们。

未命名扩展

#

声明扩展时,您可以省略名称。未命名的扩展仅在其声明的库中可见。由于它们没有名称,因此不能显式应用于解决 API 冲突

dart
extension on String {
  bool get isBlank => trim().isEmpty;
}

实现泛型扩展

#

扩展可以具有泛型类型参数。例如,以下是一些扩展内置 List<T> 类型并包含 getter、运算符和方法的代码:

dart
extension MyFancyList<T> on List<T> {
  int get doubleLength => length * 2;
  List<T> operator -() => reversed.toList();
  List<List<T>> split(int at) => [sublist(0, at), sublist(at)];
}

类型 T 基于调用方法的列表的静态类型进行绑定。

资源

#

有关扩展方法的更多信息,请参阅以下内容: