ObserveEveryValueChanged - 全てをRx化する拡張メソッド
- 2016-06-07
ブードゥーの秘術により、INotifyPropertyChanged不要で、値の変更を検知し、IObservable化します。例えばINotifyPropertyChangedじゃないところから、WidthとHeightを引き出してみます。
using Reactive.Bindings.Extensions;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.ObserveEveryValueChanged(x => x.Width).Subscribe(x => WidthText.Text = x.ToString());
this.ObserveEveryValueChanged(x => x.Height).Subscribe(x => HeightText.Text = x.ToString());
}
}
なるほど的確に追随している。ソースコードはGitHub上に公開しました。
ReactivePropertyと組み合わせることで、そのままバインダブルに変換することも可能です。
public class MyClass
{
public int MyProperty { get; set; }
}
public partial class MainWindow : Window
{
MyClass model;
public IReadOnlyReactiveProperty<int> MyClassMyProperty { get; }
public MainWindow()
{
InitializeComponent();
model = new MyClass();
this.MyClassMyProperty = model.ObserveEveryValueChanged(x => x.MyProperty).ToReadOnlyReactiveProperty();
}
}
ついでにokazukiさんが、ReactiveProperty v2.7.3に組み込んでくれましたので(今のところ).NET版では是非是非に使えます。UWP用とかXamarin用とかもきっとやってくれるでしょう(他人任せ)
仕組み
CompositionTarget.Renderingに引っ掛けて、つまり毎フレーム監視を走らせています。もともとUniRxのために作った機構を、そのままWPFに持ってきました。CompositionTarget.Renderingは、アニメーション描画などでも叩かれている比較的低下層のイベントで、これより遅いと遅れを人間が検知できちゃうので影響が出るし、これより早くても視認できないので意味がない。という、ぐらいの層です。こういった用途ではベストなところ。
毎フレーム監視がありかなしか。ゲームエンジンだと、そもそもほとんどが毎フレームごとの処理になっているので違和感も罪悪感もないのですけれど、全てがイベントドリブンで構築されている世界にそれはどうなのか。もちろん、原則はNoです。素直にINotifyPropertyChangedを書くべきだし、素直にReactivePropertyを書くべきでしょう。
ただ、アニメーションでも使われるしデバイスのインプット(LeapMotionとか)もその辺に引っ掛けるようなので、ここにちょっとプロパティに変更があるかないかのチェック入れるぐらい別にいいじゃん(どうせCPU有り余ってるんだし)、みたいな開き直りはあります。かなり。割と。
ObserveEveryValueChangedは、毎フレーム回っているような低下層の世界から、イベントドリブン(リアクティブ)な世界に引き上げるためのブリッジとしての役割があります。そう思うと不思議と、よく見えてきませんか?ただ「毎フレームポーリングかよ、ぷぷw」とかって一笑するだけだと視野が狭く、もう少しだけ一歩踏み込んで考えてみると思考実験的に面白い。私はコード片に意思を詰め込んでいくのが好きですね。哲学といってもいいし、ポエムでもある。そこには幾重も意味が込められています。