目錄

擴充方法

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

如果您喜歡觀看影片來學習,請查看此擴充方法概述。


Dart 擴充方法

概觀

#

當您使用別人的 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 衝突,這可能會很有用。以下是如何實作擴充方法 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';
// ···
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> { // <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 是根據方法呼叫的清單的靜態類型來繫結的。

資源

#

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