かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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入れてみてもいいんではないでしょうかという宣伝でした。