連載:Reactive Extensions(Rx)入門 第2回 イベント・プログラミングとRx 河合 宜文2012/01/06 |
|
本連載の第1回では、Reactive Extensions(以降、Rx)の概要とインストール方法を解説した。今回からは具体的な使い方を見ていこう。
まずはRxを利用する際の基本的な流れを見ていく。次に、Rxの持つ代表的な2つの性質であるイベントと非同期のうち、イベントを中心的に取り上げる。
●基本的な記述方法
最初に、シンプルなRx(Observableオブジェクト)のコードと、foreach文(Enumerableオブジェクト)のコードの対比を見てみよう。
|
||
同じ実行結果のRxとforeach文のコード(上:C#、下:VB) | ||
※System.Reactiveアセンブリを参照する必要がある。 |
実行結果はともに「1」から「5」が出力される。Enumerableオブジェクト(以降、「Enumerable」と略す)はPull型のシーケンス(=一連の処理の流れ)、Observableオブジェクト(以降、「Observable」と略す)はPush型のシーケンスを表す*1が、どちらも同じように記述できる。Enumerableではシーケンス(Range)があり、それを消費するもの(foreach)がある。Rxにおいてもそれは変わらず、シーケンスがあり、foreach文と同様の感覚、同じ位置でSubscribeメソッドを用いるというのが基本的な流れになる。
*1 Enumerableはデザイン・パターンでいうところのIteratorパターン、ObservableはObserverパターンを表している。 |
それでは、詳細を知るためにインターフェイスを見てみよう。Rxでの主要なインターフェイスは2つ、IObservable<T>とIObserver<T>だ。次のコードはそのコード内容である(C#の場合)。
|
|
IObservable<T>インターフェイスとIObserver<T>インターフェイス(C#の場合) |
最初に挙げたシンプルな例の中でも、この2つのインターフェイスが利用されている。IObservable<T>オブジェクトがシーケンスを表し、IObserver<T>オブジェクトが消費するものを表している。つまり、「Observable.Range」というコードから得られるオブジェクトはIObservable<int>型だ。しかし、IObserver<T>オブジェクトは見当たらない。実はIObserver<T>オブジェクトは、Subscribeメソッドに渡しているラムダ式の中で自動的に作られている。その詳細を明かすと、中では以下のようになっている。
|
||
Subscribeメソッドの引数に記述したラムダ式により生成されるIObserver<int>オブジェクトの詳細(上:C#、下:VB) |
Observer.Createメソッドには3つのデリゲートを受け取るパラメータがあり、順に、IObserver<T>インターフェイスのOnNext/OnError/OnCompletedメソッドのシグネチャと同じデリゲート型になっている。このため、その場でIObserver<T>インターフェイスを実装したインスタンスを生成できるようになっている。
しかし、それでもわざわざObserver.CreateメソッドでIObserver<T>オブジェクトを作成するのは手間だろう。そこで、IObservable<T>インターフェイスには、IObserver<T>オブジェクトのみを受け取るSubcribeメソッドだけでなく、各種のデリゲートを受け取る、複数のオーバーロードのSubcribeメソッドが、拡張メソッドによって追加されている*2。これにより、Enumerableでのforeach文と同じ感覚で利用することが可能になっているのだ。
*2 ObservableExtensionsクラス(System名前空間)に、onNext/onError/onCompletedパラメータの組み合わせパターンによる5つのメソッドが定義されている。 |
●IObserver<T>オブジェクトの詳細
最初の「同じ実行結果のRxとforeach文のコード」の例ではIObserver<T>インターフェイスのメンバのうち、OnNextメソッドのデリゲートしか記述していなかった。このOnNextは、値がオブザーバに届くたびに実行されるメソッドで、最も利用されるものだろう。残りの2つのうち、OnErrorは例外発生時に実行されるメソッド、OnCompletedはシーケンスが完了後に実行されるメソッドを表す。これら3つのメソッドのデリゲートを指定する場合の、Subcribeメソッドのコード例を示す。
|
||
OnError/OnCompletedメソッドのデリゲートを含めたSubcribeメソッドのコードの実行例(上:C#、下:VB) | ||
Doメソッドはパイプラインを通過する値に処理を加えて、元の値はそのまま後続へ渡す。今回は値が「3」のときに例外を発生させるようにした。 |
上記のコードを実行した場合、Rangeメソッドにより「1」〜「5」の値の生成が完了すると、OnCompletedメソッドが呼ばれる、もしくは途中で例外が発生するとOnErrorメソッドが呼ばれるのが確認できるだろう。なお、OnErrorメソッド、もしくはOnCompletedメソッドは、どちらか片方しか呼ばれることはない。つまり、OnErrorメソッドが呼ばれた後にOnCompletedメソッドが呼ばれたりすることはない。また、OnErrorメソッド、もしくはOnCompletedメソッドが呼ばれた後にOnNextメソッドが呼ばれることもない*3。
*3 この法則はRxに標準で用意されているメソッドではすべて守られている。また、メソッドを自作する場合でもObservable.CreateメソッドといったRx標準で用意されている生成メソッドを用いれば守られる。さらにSubscirbeメソッドの呼び出し時にもRxで用意されているObserver(前述のSystem.ObservableExtensionsクラスによる拡張メソッド、もしくはObserver.Createメソッド)を使えば自動的に守られるようになっている。このように、幾重にも渡って法則が厳守されるようになっているため、ほぼ100%この法則が破られることはないが、IObservable<T>/IObserver<T>オブジェクトをすべて一から自前で実装する場合は、この法則に反する挙動をする可能性もある。ただし、そのような実装はすべきではない。安全のためにも、IObservable<T>インターフェイスやIObserver<T>インターフェイスを実装する際は、極力、Rxに用意されているObservable.Create/Observer.Createメソッドを利用して実装すべきだろう。どうしてもそれらが利用できない場合は、法則を守るように注意深く実装する必要がある。 |
なぜこのような挙動になっているのか、そうでなければならないのかというと、foreach文で考えてみると分かりやすい。
foreach文上にOnNext/OnError/OnCompletedメソッドを当てはめたイメージ |
foreach文での列挙がすべて完了(=ループを抜ける)したら、再度、foreach文のループに入ることはない。また、列挙の最中に例外が発生したら、再度、foreach文のループに入ることもないし、列挙完了とは違うループの抜け方をするため、列挙完了の部分に入ることもない。
このような見方が可能なのは、連載の第1回で「IObservable<T>/IObserver<T>インターフェイスの成り立ち自体が、IEnumerable<T>/IEnumerator<T>インターフェイスを反転させて作られた」と説明したように、IObservable<T>オブジェクトはEnumerable<T>オブジェクトの性質をすべて守るようにできているし、また、そうでなければならないからである。
●“Dispose”の必要性
Subscribeメソッドの戻り値はIDisposableオブジェクトであるが、上記の例では変数に受けることもなく無視している。これは、RxにおいてIDisposableオブジェクトの意味が、従来使われている「解放する必要があるリソースを抱えている可能性がある」とは異なるからだ。
Rxでは元ソースがイベントの場合はイベントの「デタッチ」、タイマーの場合は「中止」、非同期の場合は「キャンセル」の意味で使われる。いずれにせよ「終了」の役割を果たす(ただしこの場合、OnCompletedメソッドは実行されない)が、それらは全て、必ずしも呼び出さなければならないものではない。むしろ、呼び出さないケースの方が多いかもしれない。よって、Subscribeメソッドの戻り値は必要がなければ無視しても問題ないのである。
●Observableオブジェクトの生成子
Enumerableにおいて、シーケンス、つまりIEnumerableインターフェイスを実装するものは、配列やList、Dictionaryなど、至る所に存在する。片や、Rxにおけるシーケンス、IObservable<T>インターフェイスを実装したものは、通常ではどこにも存在しない。そこで、生成するためのメソッドが大量に用意されている。
以下にObservableの生成子をまとめる。なお、この表はRxのStable(安定)版に用意されている生成子のみで、Experimental(実験)版のRxには、さらに追加されている。
メソッド名 | 機能 |
Create | 任意のObservableを作成 |
Defer | Subscribeされるまで、中のファクトリ・メソッド実行を遅延 |
Empty | OnCompletedメソッドのみを実行 |
FromAsyncPattern | Begin-Endパターンから作成 |
FromEvent *4 | Actionデリゲートのイベントから作成 |
FromEventPattern | EventHandlerデリゲートのイベントから作成 |
Generate | for文を模した値の発行 |
Interval | 一定時間ごとに値を発行 |
Never | 何も発行しない |
Range | 指定範囲の整数を発行 |
Repeat | 指定回数、同一の値を発行 |
Return | 1つのみの値を発行 |
Start | 指定スケジューラ*5上で即座に実行し、完了後に1つのみの値を返す |
Throw | OnErrorメソッドのみを実行 |
Timer | 一定時間後に一度のみ、もしくは一定時間後に一定周期で値を発行 |
ToAsync | 指定スケジューラ*5上で実行し、完了後に1つのみの値を返すObservableを内包したFuncデリゲートを返す |
Using | 完了後、指定したリソースをDisposeするファクトリ・メソッドを作成 |
Observableオブジェクト生成のためのObservableクラスの静的メソッド | |
*4 Windows Phone 7に同梱されているMicrosoft.Phone.Reactiveアセンブリと、Data Developer Centerで配布されているSystem.Reactiveアセンブリでは機能が異なる。その詳細は後で詳しく述べる。 | |
*5 デフォルトではスレッドプール(ThreadPool)上で実行され、Thread.StartメソッドやTask.Factory.StartNewメソッドなどの、Rxでの代替となる。スケジューラに関しては次回以降の連載で詳しく述べる。 |
メソッド名 | 機能 |
ToObservable | IEnumerable<T>オブジェクトをObservableに変換 |
IEnumerable<T>オブジェクトへの拡張メソッド |
メソッド名 | 機能 |
ToObservable | TaskオブジェクトをObservableに変換 |
Taskオブジェクトへの拡張メソッド | |
※「System.Reactive.Threading.Tasks」の「using」/「Imports」が必要。 |
これらを最初のソースとして、WhereメソッドやSelectメソッドなどおなじみのクエリ演算子や、Rxだけにある演算子を適用させていき、値を変形させ、最後にSubscribeメソッドで値を渡す、というのがRxの基本的な流れとなる。
例えば下記のコードを実行した場合、その下の図のような流れとなる。
|
||
演算子を適用させたメソッド・チェーンの例(上:C#、下:VB) |
メソッド・チェーンで流れる値のイメージ |
「Range」の一番右の四角はOnCompletedメソッドの呼び出しを表す。 |
この例ではWhereメソッドで2の倍数のみに値をフィルタリングし、Selectメソッドで値を二乗している。
それでは次のページから、本稿の主題である「イベントをRxで扱う方法」を説明しよう。
INDEX | ||
[連載]Reactive Extensions(Rx)入門 | ||
第2回 イベント・プログラミングとRx | ||
1.基本的な記述方法/IObserver<T>オブジェクトの詳細/“Dispose”の必要性/Observableオブジェクトの生成子 | ||
2.イベントとは何か? イベントをRxで扱うことの利点/FromEventメソッド&FromEventPatternメソッド | ||
3.合成のためのメソッド | ||
「連載:Reactive Extensions(Rx)入門」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|