泥庭

2014年7月27日

ReactivePropertyを使いたい人のための、ReactiveExtensions入門(その4)

Filed under: .NET, LINQ, WPF — タグ: , , , , — yone64 @ 11:28 AM

以前のバージョンは→第1回第2回第3回

今回は、PropertyChanged周りを見てみます。Rx分少なめで…。
ReactivePropertyはMVVM周りで使われるため、INotifyPropertyChangedとは切っても切れない関係があります。

var m = new Model();

var io = Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
    h => (s, e) => h(e),
    h => m.PropertyChanged += h,
    h => m.PropertyChanged -= h);

 this.Name = io.Where(e => e.PropertyName == "Name")
    .Select(e => m.Name)
    .ToReactiveProperty(m.Name);

上記のような、PropertyChangedイベントをIObservableに変換して、ToReactivePropertyするコードは何度も書くことになります。
そのため、Utilityメソッドとして定義しておくとよいでしょう。

public static IObservable<PropertyChangedEventArgs> PropertyChangedAsObservable<T>(this T source) where T : INotifyPropertyChanged
{
    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h);
}

とりあえず、Event→IO<T>変換拡張メソッドは作りますよね。で、次はPropertyNameでフィルタリングするのですが、文字列で指定するのは、いろいろと嫌なのでExpressionを引数にとれるようにします。

public static IObservable<PropertyChangedEventArgs> PropertyChangedAsObservable<TObj, TProp>(this TObj source, Expression<Func<TObj, TProp>> expression)
    where TObj : INotifyPropertyChanged
{
    var name = ((MemberExpression)expression.Body).Member.Name;

    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h)
        .Where(e => e.PropertyName == name);
}

PropertyChangedにおいてはEvetArgsが帰ってきてもうれしいことは少ないので、実際に変更された値を返却するように変更します。引数にデリゲートを追加してもよいのですが、せっかくなので既に引数に追加されているExpressionを利用したいと思います。
Expression<Func<TObj, TProp>>からFunc<TObj,TProp>を作るには、ReflectionとExpression.Compileがありますが、ここは処理速度的に有利なReflectionを使いたいと思います。(dynamicがこの目的では使えないのが残念ですね)

// .net4 まではこちら 
var func = (Func<TObj, TProp>)Delegate.CreateDelegate(typeof(Func<TObj, TProp>), typeof(TObj).GetProperty(name).GetGetMethod());

// PCL だとこっち
var func2 = (Func<TObj, TProp>)typeof(TObj).GetTypeInfo().GetRuntimeProperty(name).GetMethod.CreateDelegate(typeof(Func<TObj, TProp>));

Reflection周りは、ストアアプリ・PCL関連で大きく手が入っているので、.NET4までとPCLとでは書き方が大きく異なります。(.NET4.5だとどっちでもOK)

public static IObservable<TProp> PropertyChangedAsObservable<TObj, TProp>(this TObj source, Expression<Func<TObj, TProp>> expression)
    where TObj : INotifyPropertyChanged
{
    var name = ((MemberExpression)expression.Body).Member.Name;
    var func = (Func<TObj, TProp>)Delegate.CreateDelegate(typeof(Func<TObj, TProp>), typeof(TObj).GetProperty(name).GetGetMethod());

    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h)
        .Where(e => e.PropertyName == name)
        .Select(e => func(source));
}

CreateDelegateはそこそこ重い処理なので、実際に使うときはキャッシュしておきましょう。
あとは、ToReactivePropertyの引数で与えていた初期値をRx側で対応します。

public static IObservable<TProp> PropertyChangedAsObservable<TObj, TProp>(this TObj source, Expression<Func<TObj, TProp>> expression)
    where TObj : INotifyPropertyChanged
{
    var name = ((MemberExpression)expression.Body).Member.Name;
    var func = (Func<TObj, TProp>)Delegate.CreateDelegate(typeof(Func<TObj, TProp>), typeof(TObj).GetProperty(name).GetGetMethod());

    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h)
        .Where(e => e.PropertyName == name)
        .Select(e => func(source))
        .StartWith(func(source));
}

こういうのを一つ作っておくと、ReactivePropertyの活用がはかどりますね。
とここまで書いて、ReactivePropertyのDLLにも同じようなメソッドがあることに気付きました…

INotifyPropertyChangedExtensions.ObserveProperty

Observable.Deferメソッドで囲んであったり少し違いますが、ほぼほぼ同じですよね。(きっと
# この場合、Deferで囲むとどう動作が変わるか分かってない

コメントする »

まだコメントはありません。

RSS feed for comments on this post. TrackBack URI

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

WordPress.com Blog.

%d人のブロガーが「いいね」をつけました。