拡張メソッド(Extension methods)

拡張メソッドを利用すると、既存の型やサードパーティライブラリの型に対して機能を追加できます。元のクラスのソースコードを変更する必要はありません。IDEのコード補完には、ネイティブメソッドと並んで拡張メソッドが候補表示されます。

第三者が公開したAPIのソースは変更できないが、便利なメソッドを追加したい場合に活用します。

要求例:int.parse('42') の代わりに '42'.parseInt() を記述できるようにする。

構文形式

extension 拡張名? on 対象型 {
  // メソッド、getter、setter、演算子、静的メンバー
}Code language: JavaScript (javascript)

サンプル

// コードを1ファイルにまとめて記述。分割不要、mainが直接実行されます
extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }

  double parseDouble() {
    return double.parse(this);
  }
}

void main() {
  print('42'.parseInt());       // 42
  print('3.14'.parseDouble());  // 3.14
  print('100'.padLeft(6));      // 標準Stringメソッドと比較
}Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
42
3.14
   100Code language: CSS (css)

拡張名は省略可能です。this は拡張を呼び出したインスタンスを指します。

拡張内で定義可能なメンバー種別

拡張にはインスタンスメソッド、getter、setter、演算子、静的メンバーを定義できます。

extension StringExt on String {
  // getter
  bool get isEmptyOrBlank => trim().isEmpty;

  // インスタンスメソッド
  String repeat(int count) => List.filled(count, this).join();

  // 演算子オーバーロード
  String operator *(int count) => repeat(count);

  // 静的メンバー(拡張名からアクセス)
  static String emptyStr = '';
  static void printTip() => print("文字列拡張の静的メソッド");
}

void main() {
  var str = " dart ";
  print(str.isEmptyOrBlank);
  print("hi" * 3);
  print("hello".repeat(2));

  // 静的メンバーへアクセス
  print(StringExt.emptyStr);
  StringExt.printTip();
}Code language: PHP (php)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
false
hihihi
hellohello

文字列拡張の静的メソッドCode language: CSS (css)

名前なし拡張(匿名拡張)

拡張名を記述しない場合、同一ライブラリ内でのみ参照可能。明示呼び出しによる競合解消は利用できません。

外部から 拡張名(インスタンス) 形式で呼び出すことはできません。

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

void main() {
  print("   ".isBlank); // true
}Code language: Dart (dart)

匿名拡張内の静的メンバーは、拡張内部からのみ使用できます。

ジェネリック拡張(Generic extensions)

List<T> のような総称型に対して拡張を作成可能です。

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

void main() {
  List<int> list = [1, 2, 3, 4];
  print(list.doubleLength);          // 8
  print(-list);                      // [4,3,2,1]
  print(list.split(2));              // [[1,2],[3,4]]
}Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
8
[4, 3, 2, 1]
[[1, 2], [3, 4]]Code language: CSS (css)

静的型(static) と dynamic

dynamic変数では拡張メソッドを呼び出せない

拡張メソッドは静的解決されます。実行時に型が確定するdynamic変数では拡張が解決できません:

void main() {
  dynamic d = '2';
  // print(d.parseInt()); // 実行時に NoSuchMethodError が発生

  var v = '2';
  print(v.parseInt()); // 型推論によりStringとなり正常動作
}

extension NumberParsing on String {
  int parseInt() => int.parse(this);
}Code language: JavaScript (javascript)

理由:拡張メソッドはコンパイル時に変数の静的型をもとに紐付けられ、実行時に探索されないためです。

API名競合の解決策

複数の拡張に同名のメンバーが存在する場合、3つの対応方法があります。

1:インポート時 show / hide で競合する拡張を除外

// string_apis.dart に NumberParsing 定義
// string_apis_2.dart に NumberParsing2、同じく parseInt を持つ
import 'string_apis.dart';
import 'string_apis_2.dart' hide NumberParsing2;

void main() {
  print('42'.parseInt()); // string_apis.dart の実装が使用される
}Code language: Dart (dart)

2:拡張を明示的に呼び出す(ラッパー構文)

import 'string_apis.dart';    // NumberParsing
import 'string_apis_2.dart';  // NumberParsing2

void main() {
  // print('42'.parseInt()); // 競合によりコンパイルエラー
  print(NumberParsing('42').parseInt());
  print(NumberParsing2('42').parseInt());
}Code language: Dart (dart)

3:ライブラリをプレフィックス付きでインポート

拡張名が完全に同一の場合はプレフィックスで区別します:

import 'string_apis.dart';
import 'string_apis_3.dart' as rad;

void main() {
  print(NumberParsing('42').parseInt());
  print(rad.NumberParsing('42').parseInt());

  // 競合しない拡張メソッドは暗黙的呼び出し可能
  print('42'.parseNum());
}Code language: Dart (dart)

拡張メソッド(Extension methods)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です