👅

Flutterでvoid Function()? を null チェックなしに呼びたい

2022/01/17に公開

結論

例えば void Function()? callbackをプロパティと持つclassがある場合。下の書き方で実現できます

callback?.call() 

型自体はOptionalなので ? マークを使い、.call()のメソッドを呼び出しています

補足で ?.call()を使わない場合は下の書き方のようにnullチェックをすることになります

final callback = this.callback;
if (callback != null) {
  callback();
}

何これ

Dart にはCallable classesの機能があります。簡単にいうと call メソッドを持つ class は関数呼び出しの構文で callメソッドを呼ぶことができるものです。下の例では「スターください」と言っています。この機能はこのCallable classesの機能と言えるでしょう
document: https://dart.dev/guides/language/language-tour#callable-classes

class GiveMeStarFunction {
  void call() => print('Give me star!');
}

var gms = GiveMeStarFunction();

void main() {
  gms(); // Give me star!
}

ここからは憶測

ただこれだけだと疑問が残ります。というのもFunction classにはcallメソッドがドキュメントにも、公開されているコードを確認しても定義されてないです。

Function-class document: https://api.dart.dev/stable/2.5.2/dart-core/Function-class.html
GitHub: https://github.com/dart-lang/sdk/blob/e995cb5f7cd67d39c1ee4bdbe95c8241db36725f/sdk/lib/core/function.dart#L11

Function タイプに callメソッドが生えていないことは確認できました。ではなぜ call メソッドが呼べるのでしょうか。公式ドキュメントには下のように書かれています。

A function value, or an instance of a class with a "call" method, is a subtype of a function type, and as such, a subtype of Function.

つまり、 callメソッドが定義されているインスタンスは Function の サブタイプ使いになるようです。下記のコードでサブタイプになっていることが把握できます。

class GiveMeStarFunction {
  void call() => print('Give me star!');
}

var gms = GiveMeStarFunction();

void main() {
  void Function() f = gms; // Function タイプのインスタンスで宣言してGiveMeStarFunction型のインスタンスを代入する
  f(); // Give me star!
}

dartのランタイムの内容を把握しているわけではないので憶測ですが、見た目上やドキュメントの説明上は callメソッドが定義されているインスタンスは関数呼び出しの構文が使える になっていますが、callメソッドが定義されているインスタンスはFunctionのサブタイプ扱いになる。dartのランタイムではFunctionタイプはcallメソッド経由で呼び出される。なのでcallメソッドが定義されている場合はFunctionのサブタイプ扱いが可能になり実際に関数呼び出しの構文も使えるようになる。 になる。であれば筋が通りそうですね。

まとめ

あらためて、NullableなFunctionプロパティを利用したい場合は ?.call() できて便利!Functionの場合はnullチェック無しで単純に呼び出した方は是非使っていきましょう!

Discussion