メインコンテンツにスキップ

拡張メソッド

拡張メソッドは、既存のライブラリに機能を追加します。拡張メソッドを、それが拡張メソッドであることを知らずに使用することもあるかもしれません。たとえば、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.
}

拡張機能は、メソッドだけでなく、ゲッター、セッター、演算子などの他のメンバーも定義できます。また、拡張機能には名前を付けることができ、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` 型の変数に対して拡張メソッドを呼び出すことはできません。たとえば、次のコードは実行時例外を発生させます。

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 の競合

#

拡張メンバーがインターフェースまたは別の拡張メンバーと競合する場合、いくつかの選択肢があります。

1 つの選択肢は、`show` または `hide` を使用して公開される 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);
  }

}

拡張機能のメンバーは、メソッド、ゲッター、セッター、または演算子にすることができます。拡張機能は、静的フィールドと静的ヘルパーメソッドを持つこともできます。拡張機能宣言の外部から静的メンバーにアクセスするには、クラス変数とメソッドのように、宣言名を通じて呼び出します。

名前のない拡張

#

拡張機能を宣言するときに、名前を省略できます。名前のない拡張機能は、宣言されたライブラリでのみ表示されます。名前がないため、API の競合を解決するために明示的に適用することはできません。

dart
extension on String {
  bool get isBlank => trim().isEmpty;
}

ジェネリック拡張の実装

#

拡張機能にはジェネリック型パラメーターを含めることができます。たとえば、組み込みの `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` 型は、メソッドが呼び出されるリストの静的型に基づいてバインドされます。

リソース

#

拡張メソッドの詳細については、以下を参照してください。