內容

擴充方法為現有函式庫新增功能。您可能在不知不覺中使用擴充方法。例如,當您在 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 會根據呼叫方法的清單靜態型別來繫結。

資源

#

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