【Dart】カスケード記法(..)についてと注意が必要なmapの操作

こんにちは。エキサイトの岡島です。今回はカスケード記法(Cascade notation)についてご紹介していこうと思います。

業務中に誤ってカスケード記法を用いてmapの操作を行なっており、期待した動きにならなかったので自戒の念をこめてこの記事を書いています。ドット(.)1つの違いで思わぬ事態が起きてしまったので気をつけたいです。

カスケード記法(..)とは

カスケード記法(..)を使用すると、同じオブジェクトに対して複数の操作を連続して行うことができます。

簡単な例を以下に記します。

class Person {
  String name = '';
  int age = 0;

  void introduce() {
    print('My name is $name and I am $age years old.');
  }
}

void main() {
  final person = Person()
    ..name = '太郎'
    ..age = 5
    ..introduce();

  print(person.name);
  print(person.age);
}
  1. Personクラスのインスタンスを生成
  2. nameプロパティに太郎を設定
  3. ageプロパティに5を設定
  4. introduceメソッドを呼び出す

このステップをカスケード記法を使って記述しています。 このようにカスケード記法を使用することで、 初期化など一連の操作をまとめて書くことができるようになるという利点があります。

ちなみにカスケード記法を使わない場合は以下の様になります。

  final person = Person();
  person.name = 'Alice';
  person.age = 30;
  person.introduce();

カスケード記法でmapの操作を行うときには注意

カスケード記法でmapの処理を書くと、mapの操作が反映されないので注意が必要です。

操作が反映されない具体例

具体的にサンプルコードを用いて説明していきます。

  // 問題となるmapの例
  final list = [1, 2, 3]
    ..map((e) => e * 2)  // この操作は反映されない
    ..add(4);
  print(list); // [1, 2, 3, 4] - 2倍の操作が反映されていない

この例では、リストの要素をmapの操作で2倍にしたあとにリストに4を追加しています(処理後の結果は[2,4,6,4]になることを期待)。 しかし、処理後の中身を確認してみると[1,2,3,4]となっています。

つまり ..map((e) => e * 2)の部分は反映されておらず ..add(4)の部分だけが反映されているコードになります。

なぜこの様なことが起こるのか

この問題が発生する理由は、同じオブジェクトを操作するカスケード記法に対して、map メソッドが元のリスト (List) を直接変更せず、新しい Iterable を返す仕様にあります。

詳しくmapの操作を見てみると以下の様になっています。

  @override
  Iterable<T> map<T>(T Function(E) f) => _list.map(f);

これはmap()は元のリストを変更するのではなく、 代わりに新しいIterableを返していることを示します。

mapは新しいオブジェクトを返しているのですが、 その返り値に対してカスケード記法の操作を行うことができないということでした。

まとめ

  • カスケード記法は、常に元のオブジェクトを操作する。
  • 元のオブジェクトに対して行う処理(初期化、リスト要素の追加など)は反映される。
  • 新しいオブジェクト返す操作(map, whereなど)は、カスケード記法では無視される。

今回はカスケード記法について詳しくなることができました。 メソッドチェーンを使う箇所が誤ってカスケード記法になっており、 期待した動作にならず困っていましたが、この謎が解決できてよかったです。

詳細な仕様を知らずに使っていたことが予期せぬ動作になっていたので、基礎の重要さを思い知らされる出来事でした。

最後まで読んでいただきありがとうございました。