扩展方法
扩展方法为现有库添加功能。您可能在不知情的情况下使用扩展方法。例如,当您在 IDE 中使用代码完成时,它会建议扩展方法以及常规方法。
如果观看视频有助于您学习,请查看此扩展方法概述。
概述
#当您使用别人的 API 或实现一个被广泛使用的库时,更改 API 通常是不切实际或不可能的。但您可能仍然想添加一些功能。
例如,考虑以下将字符串解析为整数的代码
int.parse('42')
如果该功能在 String
上,可能会更好——更简洁,更易于与工具一起使用
'42'.parseInt()
要启用该代码,您可以导入一个包含 String
类扩展的库
import 'string_apis.dart';
void main() {
print('42'.parseInt()); // Use an extension method.
}
扩展可以不仅定义方法,还可以定义其他成员,例如 getter、setter 和运算符。此外,扩展可以有名称,这在 API 冲突发生时可能很有用。以下是如何使用扩展(名为 NumberParsing
)实现扩展方法 parseInt()
,该扩展作用于字符串
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
}
下一节描述如何使用扩展方法。之后是关于实现扩展方法的部分。
使用扩展方法
#像所有 Dart 代码一样,扩展方法位于库中。您已经了解了如何使用扩展方法——只需导入它所在的库,并像使用普通方法一样使用它
// Import a library that contains an extension on String.
import 'string_apis.dart';
void main() {
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.
}
这就是您通常需要了解的关于使用扩展方法的全部内容。在您编写代码时,您可能还需要了解扩展方法如何依赖于静态类型(相对于 dynamic
)以及如何解决 API 冲突。
静态类型和 dynamic
#您不能在 dynamic
类型的变量上调用扩展方法。例如,以下代码会导致运行时异常
dynamic d = '2';
print(d.parseInt()); // Runtime exception: NoSuchMethodError
扩展方法确实可以与 Dart 的类型推断一起使用。以下代码没问题,因为变量 v
被推断为具有 String
类型
var v = '2';
print(v.parseInt()); // Output: 2
dynamic
不起作用的原因是扩展方法是根据接收者的静态类型解析的。由于扩展方法是静态解析的,因此它们与调用静态函数一样快。
有关静态类型和 dynamic
的更多信息,请参阅Dart 类型系统。
API 冲突
#如果扩展成员与接口或另一个扩展成员冲突,那么您有几个选择。
一种选择是更改您导入冲突扩展的方式,使用 show
或 hide
来限制暴露的 API
// 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;
void main() {
// Uses the parseInt() defined in 'string_apis.dart'.
print('42'.parseInt());
}
另一种选择是显式应用扩展,这会导致代码看起来好像扩展是一个包装类
// 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.
void main() {
// print('42'.parseInt()); // Doesn't work.
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
}
如果两个扩展具有相同的名称,那么您可能需要使用前缀导入
// 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;
void main() {
// 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> { // <extension-name> is optional
(<member definition>)* // Can provide one or more <member definition>.
}
例如,以下是如何在 String
类上实现扩展的方法
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
double parseDouble() {
return double.parse(this);
}
}
扩展的成员可以是方法、getter、setter 或运算符。扩展也可以具有静态字段和静态辅助方法。要访问扩展声明之外的静态成员,请通过声明名称(如类变量和方法)调用它们。
未命名的扩展
#声明扩展时,您可以省略名称。未命名的扩展仅在声明它们的库中可见。由于它们没有名称,因此无法显式应用它们来解决 API 冲突。
extension on String {
bool get isBlank => trim().isEmpty;
}
实现泛型扩展
#扩展可以具有泛型类型参数。例如,以下是一些代码,它使用 getter、运算符和方法扩展了内置的 List<T>
类型
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
基于调用方法的列表的静态类型绑定。
资源
#有关扩展方法的更多信息,请参阅以下内容
除非另有说明,否则本网站上的文档反映了 Dart 3.7.1。页面上次更新于 2025-02-12。 查看源代码 或 报告问题。