iPX社員によるブログ

iPX社員が"社の動向"から"自身の知見や趣味"、"セミナーなどのおすすめ情報"に至るまで幅広い話題を投下していくブログ。社の雰囲気を感じ取っていただけたら幸いです。

ReactiveExtensions を 5日で学ぶ

お初めに

こんにちは、iPX界のReactiveExtensions部の小川です。

ReactiveExtensions(Rx)、皆さん耳にされたことはありますか。
様々な環境へ移植され、昨今ではその単語や記事を見聞きする事が多くなりました。

しかしいまいちその使い方が分からない、そんな状況が続いていました。
今回はその本家本元?である.Net Framework版(C#)について私の学習過程を記事にしてみようと思います。

rx.codeplex.com

Rxって何でしょうか

非同期やイベントなどをLINQスタイルで宣言的に書けるようにしたライブラリです。
宣言的という意味は後に体感できると思います。

事前準備

  • Visul Studio 2010以降
  • LINQの知識
  • NuGet の使い方

1日目

okazuki様のありがたい資料で学びます。
Rxを使えるようになるためのショートカットを色々探しましたが、やはり基礎が一番大事です。

Reactive extensions入門v0.1

211ページという量に圧倒されつつも、文章を読み、コードを写経します。

NuGetで Rx をインストールするには NuGet パッケージマネージャで次を入力します。

PM> Install-Package Rx-Main

Rxを使う為には学ばなければならない概念もあります。
そしてこの資料は上から順番に読んでいけば、それらも学べる素晴らしい資料です。

流れに身を任せ 3.2.4. Observable.Generateメソッド まで学びました。

2日目

前日に引き続きReactive extensions入門v0.1 を読み、写経します。

そして 3.4.1. Observable.Usingメソッド まで学びました。

通勤途中に IEnumerable<T> を IObservable<T> へ変換できる拡張メソッドの存在を知り、
せっかくなのでUsingメソッドをファイルから読み込むように変更してみました。

var source = Observable.Using(
	() => new StreamReader("UsingTest.txt"),
	sr => sr.ReadToEnd()
		.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)
		.ToObservable());

source.Subscribe(
	line => Console.WriteLine("OnNext({0})", line),
	ex => Console.WriteLine("OnError({0})", ex.Message),
	() => Console.WriteLine("Completed()"));

3日目

3.6.4. Observable.FromAsyncPatternメソッド まで学んだ後、
以降はLINQと共通する部分も多い為、写経はやめてひたすら読み進めます。

4.14. ColdからHotへ変換する拡張メソッド 以降はRx特有の機能もあるので注意深く読みます。

この時点でやりたい事を調べ、コードへ反映するという、
Rx海を泳ぐ為の最低限の力はついてきました。

4日目

基礎は分かりました、そろそろ黒い画面にも飽く頃です。
いまこそRxの拡張ライブラリReactivePropertyを用いて飛び立つときです。

製作者様である Yoshifumi Kawai様の記事で ReactiveProperty の使い方を学びます。
neue cc - ReactiveProperty : WPF/SL/WP7のためのRxとMVVMを繋ぐ拡張ライブラリ

NuGetで ReactiveProperty をインストールするには NuGet パッケージマネージャで次を入力します。

PM> Install-Package ReactiveProperty

5日目

Rxの良さを表すサンプルとして、良くマウスのドラッグが題材にされます。
ここは文化に則ってWPFCanvasにLineを描画するプログラムを作成してみたいと思います。

Model作成
public class Figure {}

public class Line : Figure
{
	public int X1 { get; set; }
	public int Y1 { get; set; }
	public int X2 { get; set; }
	public int Y2 { get; set; }
}

このX1, Y1, X2, Y2 が Canvas の Line にバインディングされる形にしたいです。

ViewModel作成
public ReadOnlyReactiveCollection<Figure> Figures { get; private set; }

public DrawingViewModel(IObservable<MouseButtonEventArgs> mouseDown, 
			IObservable<MouseEventArgs> mouseMove,
			IObservable<MouseButtonEventArgs> mouseUp)
{
	Figures = mouseMove
		.SkipUntil(mouseDown.Where(x => x.LeftButton == MouseButtonState.Pressed))
		.TakeUntil(mouseUp.Where(x => x.LeftButton == MouseButtonState.Released))
		.Select(x => x.GetPosition(null))
		.ToList()
		.Where(ps => ps.Count > 2)
		.Select(xs => new { V1 = xs.First(), V2 = xs.Last() })
		.Select(p => new Line() {
			X1 = (int)p.V1.X,
			Y1 = (int)p.V1.Y,
			X2 = (int)p.V2.X,
			Y2 = (int)p.V2.Y
		})
		.Cast<Figure>()
		.Repeat()
		.ToReadOnlyReactiveCollection();
}

先ずは ReactiveProperty の準備です。
Lineは複数作成したいので、ReadOnlyReactiveCollection<Figure> にします。

次にコンストラクタの実装です。
簡潔にするためにコンストラクタで View側からイベントの IObservable を受け取りたいと思います。

Figuresプロパティには次の意味を持たせました。

  1. マウスダウンまでを遮断する
  2. マウスアップまでを透過する
  3. マウスイベントをPointクラスへ射影する
  4. マウスダウン~アップまでをList化する
  5. ポイントが少ない場合は遮断する
  6. 扱いやすい形へ射影する
  7. Lineクラスへ射影する
  8. Figureクラスへ型変換する
  9. 以上を繰り返す IObservable を作成
View
public partial class MainWindow : Window
{
	private DrawingViewModel _viewModel;
	public MainWindow()
	{
		InitializeComponent();

		var mouseDown = Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
			h => (s, e) => h(e),
			h => this.MouseDown += h,
			h => this.MouseDown -= h);

		var mouseMove = Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
			h => (s, e) => h(e),
			h => this.MouseMove += h,
			h => this.MouseMove -= h);

		var mouseUp = Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
			h => (s, e) => h(e),
			h => this.MouseUp += h,
			h => this.MouseUp -= h);

		_viewModel = new DrawingViewModel(mouseDown, mouseMove, mouseUp);

		this.DataContext = _viewModel;
	}
}

FromEventメソッドで作成したIObservableをViewModelへ渡します。

<Grid>
	<StackPanel Orientation="Vertical">
		<ItemsControl ItemsSource="{Binding Figures}">
			<ItemsControl.ItemsPanel>
				<ItemsPanelTemplate>
					<Canvas />
				</ItemsPanelTemplate>
			</ItemsControl.ItemsPanel>
			<ItemsControl.ItemTemplate>
				<DataTemplate DataType="{x:Type local:Line}">
					<Line	Stroke="Black"
							StrokeThickness="4" 
							Fill="Black" X1="{Binding X1}" X2="{Binding X2}" Y1="{Binding Y1}" Y2="{Binding Y2}"
					/>
				</DataTemplate>
			</ItemsControl.ItemTemplate>
		</ItemsControl>
	</StackPanel>
</Grid>

ItemsControlを用いてLineが複数バインディングされるようにしました。

実行

f:id:ipx-writer:20151024214414p:plain

終わりに

Rxを用いることで非同期コードやマウス関連コードなどの、
スコープが広い変数や多数の関数を排除し、意味的にかつ局所的に記述する事が出来ました。
これが宣言的に記述出来るという事だと私は感じました。

さらに今回はView側からイベントのIObservableを渡していましたが、
XAMLのイベントから直接Observableへ変換できる機能が ReactiveProperty に存在するようです。
Expression Blend を用いるので今回の記事からは外しましたが、チャンレンジしてみたいですね。

この場を借りて素晴らしい資料やライブラリを公開してくださる方々に感謝を申し上げます。