KotlinはC#のINotifyPropertyChangedの夢を見るか?

エイプリルフールなので、ポエムを書きます。

Kotlinが仮に.NETで動いたらのお話し。全体のソースコードはこちら

背景

WPFでアプリを書く際、ライブラリを使わない場合はViewModelのクラスには毎回のようにINotifyPropertyChangedインターフェースの実装を書いていたので、面倒でした。

ところ変わって、KotlinのDelegated Propertiesという構文は衝撃的で、面白い何かに応用できそうだと思っています。

他に継承させたいクラスがある場合を考えると、INotifyPropertyChangedは必ずインターフェースとして実装したいですし、それはC#の他のライブラリでもできるので意味がないです。

結果

解説の前にまずは結果を。

class MainViewModel : NotifyPropertyChanged {
    fun Test() {
        // do your complex job

        raisePropertyChanged("AnotherProperty")
    }

    var fullName: String by PropertyChangedDelegate("Hoge Taro")
}
fun main(args: Array<String>) {
    val mainVm = MainViewModel()
    val subVm = object : NotifyPropertyChanged {
        var step: Int by PropertyChangedDelegate(9876)
    }

    mainVm.propertyChanged += { sender, e ->
        println("Notified ${e.propertyName} [$sender]")
    }
    mainVm.propertyChanged += { sender, e ->
        println("This is additional for ${e.propertyName}")
    }

    subVm.propertyChanged += { sender, e ->
        println("Notified ${e.propertyName} [$sender]")
    }

    mainVm.Test()

    println(mainVm.fullName)
    mainVm.fullName = "Piyo Jiro"
    println(mainVm.fullName)

    println(subVm.step)
    subVm.step = 12345
    println(subVm.step)
}

実行結果は以下の通りです。

Notified AnotherProperty [MainViewModel@246b179d]
This is additional for AnotherProperty
Hoge Taro
Notified fullName [MainViewModel@246b179d]
This is additional for fullName
Piyo Jiro
9876
Notified step [MainKt$main$subVm$1@25f38edc]
12345

解説

‘+=’によるイベント追加のからくり

Kotlinでは特定の名前の関数をoperatorで修飾すると、演算子の記号としてその関数を呼び出せるようになる。
このコードのEventHandlerクラスのようにplusAssign関数を記述することで、+=が使えるようになる。なお、下のinvoke関数も同じで、メソッド呼び出しのように呼び出せる。

‘fullName’プロパティの後ろの’by’とは

これがKotlinのDelegated Propertiesというもので、getterとsetterの処理を他のオブジェクトに任せることができます。
このコードのPropertyChangedDelegateクラスでは、setValue関数内でraisePropertyChanged関数によって、イベントを発行しています。つまり、C#のgetterとsetterに書いていたボイラープレートを、この機能を使って楽に書いています。
ちなみに、カッコ内は初期値とし、値があることを強制させました。(Kotlinはnullかそうでないかに厳しいので)

なぜインターフェースなのに’NotifyPropertyChanged’がインスタンスを持っている(ように見える)のか

実は、Kotlinはインターフェースの中にも状態のない実装なら書けます。

  1. インターフェースのプロパティはDelegated Propertyにできないが、メソッドの実行ならできる。
  2. インターフェース内でもthisが使えてしまうので、実際のインスタンスに固有の番号も取得できてしまう。
  3. マップなり辞書を作成して、すべてのNotifyPropertyChangedインターフェースを実装するインスタンスの中のpropertyChanged関数を通して情報を取得する。

これを組み合わせたNotifyPropertyChangedインターフェースを作ることで、少なくとも見た目はNotifyPropertyChangedインターフェースを継承するだけでいろいろ使えるようになりました。

感想

こういうのはパフォーマンスの評価も必要だと思うんですけど、ちょっと面倒なのでしてないです。

あとは、VMのインスタンスが消えた後もNotifyPropertyChangedインターフェースでのマップ・辞書のインスタンスは残ってることになるので、そのあたりも要検討です。

KotlinのDelegated Propertiesについては他サイトにわかりやすい解説があるので、そちらをオススメします。

参考記事

コメントを残す

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