かずきのBlog@hatena

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

C#でPropertyChangedを購読する方法

さて、かなり前に書いた記事があります。

こいつの欠点は購読したあと解除方法がないことですね。辛い。

今ならどうする?

ということで今ならReactive Extensionsという素敵なものがあります。例えばこんなINotifyPropetyChangedを実装したクラスがあるとします。

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var h = this.PropertyChanged;
        if (h != null)
        {
            h(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private string name;

    public string Name
    {
        get { return this.name; }
        set { this.name = value; this.OnPropertyChanged(); }
    }

}

PropertyChangedイベントを購読するにはObservableのFromEventを使ってさくっと書けます。

var p = new Person();

Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
    h => (s, e) => h(e),
    h => p.PropertyChanged += h,
    h => p.PropertyChanged -= h)
    .Where(e => e.PropertyName == "Name")
    .Subscribe(_ =>
    {
        Console.WriteLine(p.Name);
    });

p.Name = "tanaka";

Rxの基本的なFromEventの書き方ですね。なかなか慣れない…!!毎回FromEvent書くのはだるいので拡張メソッドにしてやります。

public static class PropertyChangedExtensions
{
    public static IObservable<PropertyChangedEventArgs> ObserveProperty(this INotifyPropertyChanged self, string propertyName)
    {
        return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
            h => (s, e) => h(e),
            h => self.PropertyChanged += h,
            h => self.PropertyChanged -= h)
            .Where(e => e.PropertyName == propertyName);

    }
}

こうすると、とてもすっきりします。

var p = new Person();

p.ObserveProperty("Name")
    .Subscribe(_ =>
    {
        Console.WriteLine(p.Name);
    });

p.Name = "tanaka";

もうちょっとタイプセーフに行きたいならこんな拡張メソッドも追加してやるといいです。

public static IObservable<PropertyChangedEventArgs> ObserveProperty<TProp>(this INotifyPropertyChanged self, Expression<Func<TProp>> propertyName)
{
    var name = ((MemberExpression)propertyName.Body).Member.Name;
    return self.ObserveProperty(name);
}

もっとすっきり。

var p = new Person();

p.ObserveProperty(() => p.Name)
    .Subscribe(_ =>
    {
        Console.WriteLine(p.Name);
    });

p.Name = "tanaka";

ReactivePropertyには…

こういう感じのことをしてくれる便利メソッドがあります。

// using Codeplex.Reactive.Extensions;
var p = new Person();

p.ObserveProperty(o => o.Name)
    .Subscribe(value =>
    {
        Console.WriteLine(value);
    });

p.Name = "tanaka";

なんとObservePropertyの後ろに流れる値は、プロパティの値という親切設計。ここらへんid:neueccさんのセンスが光ってると思います。細かいところですが。

まとめ

便利メソッドだけ使うということでReactiveProperty入れてみてもいいんではないでしょうかという宣伝でした。