泥庭

2015年3月10日

【ReactiveProperty】きちんとDisposeしよう

Filed under: .NET, C#, WPF — タグ: , , , — yone64 @ 3:05 PM
ReactivePropertyが依存しているReactiveExtensionsのお話なのですが、きちんとにDisposeしないと予期しない動作の原因になります。

下記サンプル
MainWindow.xaml
<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Viewbox>
            <TextBlock Text="{Binding DateTime.Value}" />
        </Viewbox>
    </Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication3
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private IDisposable _disposable;

        public MainWindow()
        {
            InitializeComponent();

            var vm = new MainViewModel();
            this.DataContext = vm;

            this._disposable = vm as IDisposable;
            this.Closing += (s, e) =>
            {
                if (this._disposable != null) this._disposable.Dispose();
            };
        }
    }
}
MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using Reactive.Bindings;
 
namespace WpfApplication3
{
    public class MainViewModel : IDisposable
    {
        public ReactiveProperty<string> DateTime { get; private set; }
 
        public MainViewModel()
        {
            this.DateTime = 
                Observable.Interval(TimeSpan.FromMilliseconds(10), NewThreadScheduler.Default)
                          .Select(_ => System.DateTime.Now.ToString("HH:mm:ss.fff")).ToReactiveProperty();
        }
 
        void IDisposable.Dispose()
        {
            this.DateTime.Dispose();
        }
    }
}
タイマーで時間を表示するだけの簡単なサンプルですが、ReactivePropertyをDisposeしないと終了しないアプリケーションになってしまいます。
この場合は、NewThreadSchedulerによって新しいThreadが立っているので当然の結果なのですが、Rxを使うと簡単にThreadを起こせるので、神経質にDisposeしていたほうが良い気がしました。

というわけで、基底クラスとして次のようなものを用意するようにしています。
ViewModelBase.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication3
{
    public abstract class ViewModelBase : IDisposable
    {
        protected CompositeDisposable Disposable { get; private set; }

        public ViewModelBase()
        {
            this.Disposable = new CompositeDisposable();
        }

        void IDisposable.Dispose()
        {
            this.Disposable.Dispose();
        }
    }
}
CompositeDisposableは、Rxが提供しているIDisposableをまとめるためのクラスです。
これを利用すると、MainViewModelが次のように少し簡易になります。
MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using Reactive.Bindings;
 
namespace WpfApplication3
{
    public class MainViewModel : ViewModelBase
    {
        public ReactiveProperty<string> DateTime { get; private set; }
 
        public MainViewModel()
        {
            this.DateTime = 
                Observable.Interval(TimeSpan.FromMilliseconds(10), NewThreadScheduler.Default)
                          .Select(_ => System.DateTime.Now.ToString("HH:mm:ss.fff"))
                          .ToReactiveProperty()
                          .AddTo(this.Disposable); // かずきさん指摘により修正

            // this.Disposable.Add(this.DateTime);
        }
    }
}
ToReactiveProperty()とDisposable.Add()をまとめてうまく書けるといいんですけどね。↓のような、拡張メソッドを増やすのしか思いつきませんでした。
public static class ViewModelBaseEx
{
    public static ReactiveProperty<T> ToReactiveProperty<T>(this IObservable<T> souce, CompositeDisposable disposable)
    {
        var rp = souce.ToReactiveProperty();
        disposable.Add(rp);
        return rp;
    }
}

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で囲むとどう動作が変わるか分かってない

2014年7月13日

Reactive Extensions

Filed under: .NET, LINQ — タグ: , — yone64 @ 12:37 AM

Room Metro #26で、ReactiveExtensionsについて発表してきました。
北陸のC#MVPに軽いテクハラを受けたりしましたが、それも今となってはいい思い出。

ソースコード一式はこちらからどうぞ
https://github.com/yone64/RoomMetro26

2014年7月1日

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

Filed under: .NET, LINQ, WPF — タグ: , , , — yone64 @ 10:21 PM

以前のバージョンは → 第1回第2回
サブタイトルをつけておけばよかったなと思いつつ、適当なサブタイトルが思いつかないので今回もそのまま行きます。

さて、MVVM方式(?)でアプリケーションを作成していると、ViewModelが親子構造になることはよくあります。
で、その場合に親ViewModelが子ViewModelのProperty変更を監視したいということも少なからずあります。
ちなみに、非ReactivePropertyな世界だと、PropertyのSetterにイベントの着脱を仕込むのが一般的なのではないでしょうか(と勝手に思ってます)。

private SubContentViewModel _subContent;
public SubContentViewModel SubContent
{
    get
    {
        return _subContent;
    }
    set
    {
        if (_subContent == value) return;
        if (_subContent != null) _subContent.PropertyChanged -= PropertyChanged;
        _subContent = value;
        if (_subContent != null) _subContent.PropertyChanged += PropertyChanged;
    }
}

これを、ReactivePropertyを利用して実現してみます。何も考えないと以下のようにSelectManyで簡単に実現できるように思えます。
# SubContentとTextはそれぞれReactivePropertyです。

this.SubContent
    .Where(s => s != null)
    .SelectMany(s => s.Text)
    .Subscribe(t => Console.WriteLine(t));

しかし、これだと一度購読を開始したSubContentがいつまでたっても解放されないため、メモリリークなどの原因になってしまいます。
これを以下のように記述することで、親ViewModelから参照されなくなった子ViewModelの変更購読を解放することができるようになります。

this.SubContent
    .Select(s => s == null ? null : s.Text.Subscribe(t => Console.WriteLine(t)))
    .Scan(new Tuple<IDisposable, IDisposable>(null, null), (a, b) => Tuple.Create(a.Item2, b))
    .Subscribe(t => { if (t.Item1 != null) t.Item1.Dispose(); });

長いな…

2014年6月24日

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

Filed under: .NET, LINQ, WPF — タグ: , , , , — yone64 @ 1:12 AM

第2回があったとは。。。

それはさておき、Rxが便利な分野といえば、イベントと非同期ですね。非同期はTaskとasync/awaitに譲った感もありますが、イベント合成はRxの独壇場です。(たぶん
そして、Rxでイベント合成のサンプルで間違いなく出てくるのが、マウスのDragですね。MouseDown→MouseMove→MouseUpの流れをうまく表現できるのが素晴らしい。

というわけで、いきなりコード

<Window x:Class="ReactiveWPF.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
    <Canvas x:Name="Canvas" Background="Transparent">
        <Polygon Points="{Binding Points.Value}" Stroke="Black" StrokeThickness="2"/>
    </Canvas>
</Window>

XAMLは、何のひねりもないので問題ないと思います。

/// <summary>
/// Window1.xaml の相互作用ロジック
/// </summary>
public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        
        this.Points = Canvas.MouseDownAsObservable().Do(_ => Canvas.CaptureMouse()).Select(e => e.GetPosition(Canvas))
            .SelectMany(_ => Canvas.MouseMoveAsObservable().Select(e => e.GetPosition(Canvas)), (s, e) => new {s, e})
            .Select(a => new PointCollection(new []{a.s, new Point(a.s.X, a.e.Y), a.e, new Point(a.e.X, a.s.Y) }))
            .TakeUntil(Canvas.MouseUpAsObservable().Do(_ => Canvas.ReleaseMouseCapture()))
            .ToReactiveProperty();

        this.Points
            .Subscribe(
            _ => Console.WriteLine("Next:" + _),
            () => Console.WriteLine("Complete"));

        this.DataContext = this;
    }

    public ReactiveProperty<PointCollection> Points { get; private set; }
}

public static class FrameworkElementEx
{
    public static IObservable<MouseButtonEventArgs> MouseDownAsObservable(this FrameworkElement source)
    {
        return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
            h => (s, e) => h(e),
            h => source.MouseDown += h, 
            h => source.MouseDown -= h);
    }

    public static IObservable<MouseEventArgs> MouseMoveAsObservable(this FrameworkElement source)
    {
        return Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
            h => (s, e) => h(e),
            h => source.MouseMove += h,
            h => source.MouseMove -= h);
    }

    public static IObservable<MouseButtonEventArgs> MouseUpAsObservable(this FrameworkElement source)
    {
        return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
            h => (s, e) => h(e),
            h => source.MouseUp += h,
            h => source.MouseUp -= h);
    }
}

C#側もそんなに問題ないですね。詳しくは、@ITの@neueccさんの記事とか@xin9leさんのブログとかを参照してください。ほぼ、そのままですね。
# 手抜きなので、イベントを拾ってプロパティーを変更して、自分自身とBindingしてるという微妙なコードになっていますが、ここではそこは大きな問題ではないです。
これで、Mouseによる範囲選択ができた。と思ったのですが、現実はそんなに甘くありませんでした。

MouseでDrag処理を行った場合、MouseUp時に何かしら確定処理を行うことがほとんどだと思います。
#矩形に含まれるオブジェクトを選択するとか、動的オブジェクトを永続オブジェクトに変更するとか…

しかし、この終了処理を記述する良い場所がないのです。
まず思いつくのが、SubscribeのCompleteだと思うのですが、これはMouseUpイベントが発生し、一連のMouseイベント購読が終わっても、呼び出されることがありません。(Hotだから?
次に、ReleaseMouseCapture()を呼び出している、Doメソッドの内部ですが、ここではObservableなStreamを流れてくるPointCollectionに対してアクセスすることができません。

解決策は、Rxの外部に変数を宣言して、変数のキャプチャを行うことになると思うのですが、これはこれでいまいちですよね。

2014年6月20日

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

Filed under: .NET, LINQ, WPF — タグ: , , , , — yone64 @ 11:52 PM

タイトル書いてみて、そんなやつおらんやろ。ってなりましたが、まぁ気にせずに。
# そんなことよりRibbonの記事の続きをって人もいないやろうし、

最近、ReactivePropertyを使うようになって、Reactive Extensions(以後、Rx)を知らなさすぎるなぁと、思い知ったのでメモがてら残してきたいと思います。
ところで、ReactivePropertyってなに?って人は、ひとまず
コチラに。超ざっくり言うとMVVMで便利なライブラリです。

public class MainViewModel
{
    public ReactiveCommand Command { get; private set; }

    public MainViewModel()
    {
        this.Command = new ReactiveCommand();
        this.Command.Subscribe(_ => Console.WriteLine("one"));
    }
}

↑こんな感じでVM書いて、CommandをBindingしておけば、クリック時に”one”がコンソールに出力されます。簡単ですね。
そのうえ、イベントベースのRx(Hotっていうやつ?)なので分配もできます。はい。

public class MainViewModel
{
    public ReactiveCommand Command { get; private set; }

    public MainViewModel()
    {
        this.Command = new ReactiveCommand();
        this.Command.Subscribe(_ => Console.WriteLine("one"));
        this.Command.Subscribe(_ => Console.WriteLine("two"));
    }
}

↑のように書くと、”one”も”two”も表示されますね。なるほど。ところで、分配っていつ行われるか気になったので、ちょっと調べてみました。

this.Command = new ReactiveCommand();

var io = this.Command
    .Do(_ => Console.WriteLine("do"))
    .Where(_ => Condition()));

io.Subscribe(_ => Console.WriteLine("one"));
io.Subscribe(_ => Console.WriteLine("two"));

↑ Subscribeまでの間に、共通の条件を突っ込みました。この場合、クリック1回で”do”は何回表示されるでしょうか?
結果は、2回です。ということはCondition()の評価は2回行われてるんですね。これはあまりうれしくない。

1回で済ます方法はないんでしょうか?(教えて偉い人)
# イメージ的にはIE.ToList()なのりで出来そうなのですが…

WordPress.com で無料サイトやブログを作成.