Rx 要素はいらないんだけど ViewModel を簡単に書くために ReactiveProperty を使いたい人向け機能


ReactiveProperty は便利そうだけど、パッケージ追加すると Reactive Extensions 由来の機能がたくさんついてくるのは、ちょっと難しいなぁ…と思っている人が一定数いると思っていたので、先ほどリリースした ReactiveProperty v7 では新たに ReactiveProperty.Core というパッケージを作成しました。

提供機能

以下の 2 つのクラスが含まれています。

  • ReactivePropertySlim<T>
  • ReadOnlyReactivePropertySlim<T>

Reactive Extensions への参照も無い完全に独立したパッケージです。ターゲットプラットフォームは .NET Standard 2.0 になります。

使い方

プロジェクトに ReactiveProperty.Core パッケージを NuGet から追加します。
そして、ViewModel などで以下のように使えます。

using Reactive.Bindings;
using System.ComponentModel;

namespace WpfApp44
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public ReactivePropertySlim<string> FirstName { get; } = new ReactivePropertySlim<string>();
        public ReactivePropertySlim<string> LastName { get; } = new ReactivePropertySlim<string>();
        private ReactivePropertySlim<string> FullNameSource { get; } = new ReactivePropertySlim<string>();
        public ReadOnlyReactivePropertySlim<string> FullName { get; }

        public MainWindowViewModel()
        {
            FullName = FullNameSource.ToReadOnlyReactivePropertySlim();
            FirstName.PropertyChanged += (_, __) => UpdateFullName();
            LastName.PropertyChanged += (_, __) => UpdateFullName();
        }

        private void UpdateFullName()
        {
            FullNameSource.Value = $"{FirstName.Value} {LastName.Value}";
        }

    }
}

Rx 使ってる人から見ると、なんかまどろっこしい感じもありますがラムダ式を受け取る Subscribe メソッドも System.Reactive パッケージで追加されるものなので、ReactiveProperty.Core を使う場合は ReactiveProperty の値が更新されたときの処理は PropertyChanged イベントを使うのが良さそうです。

ReadOnlyReactivePropertySlim は ReactivePropertySlim を読み取り専用で外部に公開したいときに使うくらいしか用途はないように感じます。

この ViewModel を画面にバインドさせてみましょう。

<Window x:Class="WpfApp44.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp44"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <StackPanel>
        <Label Content="First name:" />
        <TextBox Text="{Binding FirstName.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <Label Content="Last name:" />
        <TextBox Text="{Binding LastName.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

        <Label Content="Full name:" />
        <TextBlock Text="{Binding FullName.Value}" />
    </StackPanel>
</Window>

実行すると以下のように動きます。

使えない機能

PropertyChanged イベントを UI スレッドで実行する機能

ReactiveProperty では PropertyChanged イベントを自動で UI スレッドで発行していました。ReactivePropertySlim では、値を設定したときのスレッドで PropertyChanged イベントが発行されます。UI コントロールとバインドしている場合はエラーになるので、ReactivePropertySlim への値の設定は UI スレッド上で行いましょう。

ただ、この機能は UI スレッドが複数あると破綻していた機能なので個人的には ReactiveProperty からも排除したい気持ちもあります…(あっ、v7 のタイミングですればよかった!!!)

値の検証機能

ReactiveProperty は DataAnnotations やカスタムロジックでの入力値の検証機能がありますが ReactivePropertySlim にはありません。

Rx のメソッド

Select メソッドや Where メソッドやラムダ式を受け取る Subscribe メソッドなどは Rx で提供されているので使えません。

Slim 系のクラスに対して Rx のメソッドが使いたくなった場合は System.Reactive を追加で導入してください。

System.Reactive を追加すると先ほどの ViewModel は、以下のようにスッキリします。

using Reactive.Bindings;
using System.ComponentModel;
using System.Reactive.Linq;

namespace WpfApp44
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public ReactivePropertySlim<string> FirstName { get; } = new ReactivePropertySlim<string>();
        public ReactivePropertySlim<string> LastName { get; } = new ReactivePropertySlim<string>();
        public ReadOnlyReactivePropertySlim<string> FullName { get; }

        public MainWindowViewModel()
        {
            FullName = FirstName.CombineLatest(LastName, (f, l) => $"{f} {l}")
                .ToReadOnlyReactivePropertySlim();
        }
    }
}

便利系拡張メソッド

POCO のクラスのプロパティ変更を監視するための ObserveProperty 拡張メソッドを始め色々な拡張メソッドが ReactiveProperty で定義されていますが、ReactiveProperty.Core には、これらも入っていません。使いたい場合は、ReactiveProperty.Core ではなく ReactiveProperty パッケージを使用してください。

ReactiveProperty のコレクションや便利クラス

これも便利系拡張メソッドと同じです。

まとめ

本当に INotifyPropertyChanged インターフェースを実装した値を保持するだけのライトなクラスが欲しい人は ReactiveProperty.Core も試してみてください。
そこから始めて System.Reactive に手を伸ばしたり、フル機能が欲しくなったタイミングで ReactiveProperty.Core から ReactiveProperty にパッケージを差し替えるだけでフル機能に移行できるので、個人的にはお勧めです。