跳到主要內容

擴充方法

擴充方法為現有的函式庫新增功能。您可能在不知情的情況下使用擴充方法。例如,當您在 IDE 中使用程式碼完成功能時,它會建議擴充方法以及常規方法。

如果觀看影片有助於您學習,請查看此擴充方法概觀。


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 衝突時很有幫助。以下說明如何使用擴充功能(名為 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';

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 是根據呼叫方法的列表的靜態類型綁定的。

資源

#

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