跳到主要內容

擴充套件方法

擴充套件方法為現有庫添加了功能。你可能在不知情的情況下使用了擴充套件方法。例如,當你在 IDE 中使用程式碼補全時,它會在常規方法旁邊建議擴充套件方法。

如果你喜歡透過影片學習,可以看看這個擴充套件方法概述影片。

在新標籤頁上觀看 YouTube 影片:“Dart 擴充套件方法”

概覽

#

當你使用別人的 API 或者實現一個廣泛使用的庫時,通常修改 API 是不切實際或不可能的。但你可能仍然希望新增一些功能。

例如,考慮以下將字串解析為整數的程式碼

dart
int.parse('42')

如果這個功能直接放在 String 上,可能會更好——程式碼更短,也更容易與工具一起使用

dart
'42'.parseInt()

要啟用該程式碼,你可以匯入一個包含對 String 類擴充套件的庫

dart
import 'string_apis.dart';

void main() {
  print('42'.parseInt()); // Use an extension method.
}

擴充套件不僅可以定義方法,還可以定義其他成員,例如 getter、setter 和運算子。此外,擴充套件可以有名稱,這有助於解決 API 衝突。以下是如何實現擴充套件方法 parseInt() 的示例,它使用一個操作字串的擴充套件(命名為 NumberParsing

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';

void main() {
  print('42'.padLeft(5)); // Use a String method.
  print('42'.parseInt()); // Use an extension method.
}

這通常就是你使用擴充套件方法所需瞭解的全部內容。在編寫程式碼時,你可能還需要了解擴充套件方法如何依賴於靜態型別(而非 dynamic)以及如何解決 API 衝突

靜態型別與 dynamic

#

你不能在型別為 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;

void main() {
  // 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.

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

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 類上實現擴充套件的方法

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 是根據呼叫方法的列表的靜態型別繫結的。

資源

#

有關擴充套件方法的更多資訊,請參閱以下內容