かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

ReactiveProperty v2.1.3をリリースしました

v2.1.3

破壊的変更

  • ObserveElementProperty で値に変更があったインスタンスも通知できるようにしました。

解説

2.1.2で追加したObserveElementPropertyをちょこっと変えました。ごめんなさい。互換性壊しました!

そのかわり便利になってます。例えば、ObservableCollectionの要素から特定のプロパティを監視して、リアルタイムに条件を満たす要素だけを集めておくといったことが簡単?に出来るようになっています。

例えば以下のようなクラスがあるとします。

class Person : BindableBase
{
    private string name;

    public string Name
    {
        get { return this.name; }
        set { this.SetProperty(ref this.name, value); }
    }

    private bool isRemoved;

    public bool IsRemoved
    {
        get { return this.isRemoved; }
        set { this.SetProperty(ref this.isRemoved, value); }
    }

}

IsRemovedがTrueの要素だけをリアルタイムに集めるにはこんな感じにします。

// ソース
var col = new ObservableCollection<Person>(
    Enumerable.Range(0, 10).Select(x => new Person { Name = "tanaka" + x, IsRemoved = x % 4 == 0 }));

// IsRemovedがtrueの要素を集める
var removed = new ObservableCollection<Person>();
col.ObserveElementProperty(x => x.IsRemoved)
    .Subscribe(x =>
    {
        if (x.Value)
        {
            removed.Add(x.Instance);
        }
        else
        {
            removed.Remove(x.Instance);
        }
    });

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka0 tanaka4 tanaka8

col[1].IsRemoved = true;

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka0 tanaka4 tanaka8 tanaka1

col[0].IsRemoved = false;

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka4 tanaka8 tanaka1

ObserveElementPropertyにわたってくる引数のValueでプロパティ値が取れて、Instanceでプロパティを持ってるクラスのインスタンスが取れます。上記の例では使ってませんが、PropertyでPropertyInfoも取得できます。

仕上げ

上記の例では、コレクションに要素が削除されたときに対応できません。ということで、本当にリアルタイムにIsRemovedがTrueの要素を集めるコードはコレクションの削除の処理も追加して以下のような感じになります。

// ソース
var col = new ObservableCollection<Person>(
    Enumerable.Range(0, 10).Select(x => new Person { Name = "tanaka" + x, IsRemoved = x % 4 == 0 }));

// IsRemovedがtrueの要素を集める
var removed = new ObservableCollection<Person>();
col.ObserveElementProperty(x => x.IsRemoved)
    .Subscribe(x =>
    {
        if (x.Value)
        {
            removed.Add(x.Instance);
        }
        else
        {
            removed.Remove(x.Instance);
        }
    });
// 要素が削除されたときの対応
col.ObserveRemoveChangedItems()
    .Merge(col.ObserveReplaceChangedItems().Select(x => x.OldItem))
    .Subscribe(xs =>
    {
        foreach (var x in xs)
        {
            if (x.IsRemoved) { removed.Remove(x); }
        }
    });
// 何か大きな変更があったときの対応
col.ObserveResetChanged()
    .Subscribe(_ =>
    {
        removed.Clear();
        foreach (var x in col.Where(x => x.IsRemoved))
        {
            removed.Add(x);
        }
    });

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka0 tanaka4 tanaka8

col[1].IsRemoved = true;

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka0 tanaka4 tanaka8 tanaka1

col[0].IsRemoved = false;

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka4 tanaka8 tanaka1

// 追加OK
col.Add(new Person { Name = "okazuki", IsRemoved = true });
Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka4 tanaka8 tanaka1 okazuki

// 削除OK
col.Remove(removed[0]);
Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka8 tanaka1 okazuki

// クリアOK
col.Clear();
Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // なし