扩展方法

Last updated: ... / Reads: 35 Edit

扩展方法将功能添加到现有库。 您可能会在不知不觉中使用扩展方法。 例如,在 IDE 中使用代码完成时, 它建议将扩展方法与常规方法放在一起。

概述

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

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

int.parse('42')

它可能很好 - 更短且更易于与工具一起使用 - 改为启用该功能:String

'42'.parseInt()

要启用该代码,请执行以下操作: 您可以导入包含类扩展的库:String

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

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

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

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

使用扩展方法

像所有 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.

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

静态类型和动态类型

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

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

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

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

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

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

API 冲突

如果扩展成员与 接口或与其他扩展成员, 那么你有几个选择。

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

// 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());

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

// 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());

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

// 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

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

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 扩展方法基础
  • 功能规范
  • 扩展方法示例

Comments

Make a comment