目录

扩展方法为现有库添加了功能。你可能在不知情的情况下使用了扩展方法。例如,当你在 IDE 中使用代码补全时,它会建议扩展方法和常规方法。

如果观看视频有助于你学习,请查看扩展方法的此概述。

概览

#

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

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

dart
int.parse('42')

最好将该功能放在 String 上,以便使用工具时更短、更轻松

dart
'42'.parseInt()

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

dart
import 'string_apis.dart';
// ···
print('42'.parseInt()); // Use an extension method.

扩展不仅可以定义方法,还可以定义其他成员,例如 getter、setter 和运算符。此外,扩展可以有名称,如果出现 API 冲突,这会很有帮助。下面是如何使用扩展(名为 NumberParsing)实现扩展方法 parseInt(),该扩展对字符串进行操作

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

下一部分介绍如何使用扩展方法。之后的部分介绍如何实现扩展方法。

使用扩展方法

#

与所有 Dart 代码一样,扩展方法位于库中。你已经了解如何使用扩展方法,只需导入它所在的库,然后像普通方法一样使用它

dart
// Import a library that contains an extension on String.
import 'string_apis.dart';
// ···
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.

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

静态类型和动态类型

#

你无法对类型为 dynamic 的变量调用扩展方法。例如,以下代码会导致运行时异常

dart
dynamic d = '2';
print(d.parseInt()); // Runtime exception: NoSuchMethodError

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

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

dynamic 不起作用的原因是扩展方法根据接收者的静态类型进行解析。由于扩展方法是静态解析的,因此它们与调用静态函数一样快。

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

API 冲突

#

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

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

dart
// Defines the String extension method parseInt().
import 'string_apis.dart';

// Also defines parseInt(), but hiding NumberParsing2
// hides that extension method.
import 'string_apis_2.dart' hide NumberParsing2;

// ···
// Uses the parseInt() defined in 'string_apis.dart'.
print('42'.parseInt());

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

dart
// Both libraries define extensions on String that contain parseInt(),
// and the extensions have different names.
import 'string_apis.dart'; // Contains NumberParsing extension.
import 'string_apis_2.dart'; // Contains NumberParsing2 extension.

// ···
// print('42'.parseInt()); // Doesn't work.
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());

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

dart
// Both libraries define extensions named NumberParsing
// that contain the extension method parseInt(). One NumberParsing
// extension (in 'string_apis_3.dart') also defines parseNum().
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;

// ···
// print('42'.parseInt()); // Doesn't work.

// Use the ParseNumbers extension from string_apis.dart.
print(NumberParsing('42').parseInt());

// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());

// Only string_apis_3.dart has parseNum().
print('42'.parseNum());

如示例所示,即使使用前缀导入,也可以隐式调用扩展方法。仅当显式调用扩展时,才需要使用前缀来避免名称冲突。

实现扩展方法

#

使用以下语法创建扩展

extension <extension name>? on <type> {
  (<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;
}

实现泛型扩展

#

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

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 根据调用方法的列表的静态类型进行绑定。

资源

#

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