目次

拡張メソッド

拡張メソッドは、既存のライブラリに機能を追加します。 知らないうちに拡張メソッドを使っているかもしれません。たとえば、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.

拡張機能はメソッドだけでなく、ゲッター、セッター、演算子などの他のメンバーも定義できます。 また、拡張機能には名前を付けることができ、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の競合

#

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

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;

// ···
// Uses the parseInt() defined in 'string_apis.dart'.
print('42'.parseInt());

もう1つの選択肢は、拡張を明示的に適用することです。これにより、拡張がラッパークラスであるかのように見えるコードが生成されます。

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);
  }
}

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

名前のない拡張

#

拡張を宣言するときに、名前を省略できます。 名前のない拡張は、宣言されているライブラリでのみ表示されます。 名前がないため、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は、メソッドが呼び出されるリストの静的型に基づいてバインドされます。

リソース

#

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