Contenu connexe
Similaire à Reactive extensions入門v0.1
Similaire à Reactive extensions入門v0.1 (20)
Reactive extensions入門v0.1
- 2. 改版履歴
版数 内容 日付
0.1 基本メソッドの説明を書いた初版作成 2012/03/05
ii
- 3. 内容
1. はじめに.............................................................................................................................................................. 1
1.1. 前提環境 ............................................................................................................................................................ 1
1.2. 対象読者 ............................................................................................................................................................ 1
1.3. 謝辞 ................................................................................................................................................................... 1
2. REACTIVE EXTENSIONS とは ........................................................................................................................ 2
2.1. OBSERVER パターン .......................................................................................................................................... 2
2.2. REACTIVE EXTENSIONS で提供されている機能 ................................................................................................ 2
2.3. IOBSERVALBE<T>ンターフェースと IOBSERVER<T>ンターフェース ..................................................... 3
2.4. REACTIVE EXTENSIONS の機能を使った書き直し ............................................................................................ 8
3. IOBSERVABLE<T>のファクトリメソッド ...................................................................................................... 11
3.1. 基本的なフゔクトリメソッドの使用 ............................................................................................................... 11
3.2. 指定された値を返す IOBSERVABLE<T>を返すフゔクトリメソッド .............................................................. 12
3.2.1. Observable.Return メソッド ................................................................................................................. 12
3.2.2. Observable.Repeat メソッド.................................................................................................................. 12
3.2.3. Observable.Range メソッド ................................................................................................................... 13
3.2.4. Observable.Generate メソッド .............................................................................................................. 14
3.2.5. Observable.Defer メソッド .................................................................................................................... 15
3.2.6. Observable.Create メソッド .................................................................................................................. 17
3.2.7. Observable.Throw メソッド .................................................................................................................. 18
3.3. ここまでに紹介した IOBSERVABLE<T>の動作 ............................................................................................... 19
3.4. リソースの確保を伴う IOBSERVABLE<T> ...................................................................................................... 19
3.4.1. Observable.Uting メソッド .................................................................................................................... 20
3.5. 時間と共に値を発行する IOBSERVABLE<T> .................................................................................................. 21
3.5.1. Observable.Timer メソッド ................................................................................................................... 21
3.5.2. Observable.Interval メソッド ................................................................................................................ 23
3.5.3. Observable.Generate メソッド .............................................................................................................. 24
3.6. COLD な IOBSERVABLE<T>と HOT な IOBSERVABLE<T> .............................................................................. 25
3.6.1. Observable.FromEvent メソッド .......................................................................................................... 28
i
- 4. 3.6.2. Observable.Start メソッド..................................................................................................................... 30
3.6.3. Observable.ToAsync メソッド ............................................................................................................... 32
3.6.4. Observable.FromAsyncPattern メソッド ............................................................................................. 33
4. IOBSERVABLE の拡張メソッド ...................................................................................................................... 35
4.1. LINQ のメソッド ............................................................................................................................................ 35
4.2. 単一の値を取得するメソッド ......................................................................................................................... 38
4.2.1. First メソッドと Last メソッド ............................................................................................................. 38
4.2.2. FirstOrDefault メソッドと LastOrDefault メソッド ........................................................................... 41
4.2.3. ElementAt メソッド ............................................................................................................................... 42
4.2.4. ElementAtOrDefault メソッド .............................................................................................................. 45
4.2.5. Single メソッド ....................................................................................................................................... 46
4.2.6. SingleOrDefault メソッド...................................................................................................................... 50
4.3. 値と飛ばす、値を拾うメソッド ...................................................................................................................... 52
4.3.1. Skip と Take メソッド ............................................................................................................................ 52
4.3.2. Repeat メソッドとの組み合わせ ............................................................................................................ 53
4.3.3. SkipWhile と TakeWhile メソッド ........................................................................................................ 54
4.3.4. SkipUntil と TakeUntil メソッド .......................................................................................................... 55
4.3.5. SkipUntil と TakeUntil を使ったドラッグの処理 ................................................................................. 56
4.3.6. SkipLast と TakeLast メソッド ............................................................................................................. 61
4.4. DO メソッド .................................................................................................................................................... 62
4.4.1. Do メソッド使用時の注意点 ................................................................................................................... 64
4.5. エラー処理関連のメソッド ............................................................................................................................. 64
4.5.1. Catch メソッド ....................................................................................................................................... 64
4.5.2. Finally メソッド ..................................................................................................................................... 67
4.5.3. OnErrorResumeNext メソッド ............................................................................................................. 70
4.5.4. Retry メソッド ........................................................................................................................................ 72
4.6. IOBSERVABLE<T>の値を収集するメソッド ................................................................................................... 75
4.6.1. ToArray メソッド ................................................................................................................................... 75
4.6.2. ToDictionary メソッド ........................................................................................................................... 76
4.6.3. ToList メソッド ...................................................................................................................................... 77
4.6.4. ToLookup メソッド ................................................................................................................................ 78
ii
- 5. 4.6.5. Max メソッドと Min メソッドと Average メソッド ............................................................................. 79
4.6.6. MaxBy メソッドと MinBy メソッド ...................................................................................................... 82
4.6.7. Count メソッドと LongCount メソッド ................................................................................................ 83
4.6.8. Any メソッド .......................................................................................................................................... 84
4.6.9. All メソッド ............................................................................................................................................ 85
4.6.10. Aggregate メソッド ................................................................................................................................ 86
4.6.11. Scan メソッド ......................................................................................................................................... 91
4.6.12. GroupBy メソッド .................................................................................................................................. 93
4.6.13. GroupByUntil メソッド ......................................................................................................................... 95
4.7. IOBSERVABLE<T>から IENUMERABLE<T>への変換メソッド ....................................................................... 99
4.7.1. ToEnumerable メソッド ........................................................................................................................ 99
4.7.2. Latest メソッド .................................................................................................................................... 101
4.7.3. MostRecent メソッド ........................................................................................................................... 104
4.7.4. Next メソッド ....................................................................................................................................... 106
4.8. TOEVENT メソッド........................................................................................................................................ 108
4.9. 重複を排除するメソッド ............................................................................................................................... 109
4.9.1. Distinct メソッド .................................................................................................................................. 109
4.9.2. Distinct メソッドのオーバーロード ..................................................................................................... 110
4.9.3. DistinctUntilChanged メソッド .......................................................................................................... 113
4.10. BUFFER メソッドと WINDOW メソッド ........................................................................................................ 114
4.10.1. 数でまとめる Buffer メソッドのオーバーロード ................................................................................. 114
4.10.2. 時間でまとめる Buffer メソッドのオーバーロード ............................................................................. 118
4.10.3. 任意のタイミングで値をまとめる Buffer メソッドのオーバーロード ................................................ 121
4.10.4. 時間と数でまとめる Buffer メソッドのオーバーロード ...................................................................... 125
4.10.5. Window メソッド.................................................................................................................................. 127
4.11. 発行された値にたいして時間でフゖルタ・操作するメソッド ..................................................................... 130
4.11.1. Sample メソッド ................................................................................................................................... 130
4.11.2. Throttle メソッド ................................................................................................................................. 133
4.11.3. Delay メソッド ..................................................................................................................................... 135
4.11.4. Timeout メソッド ................................................................................................................................. 136
4.12. 時間に関する情報を付与する拡張メソッド .................................................................................................. 138
iii
- 6. 4.12.1. Timestamp メソッド ............................................................................................................................ 139
4.12.2. TimeInterval メソッド ......................................................................................................................... 140
4.13. 型変換を行う拡張メソッド ........................................................................................................................... 141
4.13.1. Cast メソッド ....................................................................................................................................... 141
4.13.2. OfType メソッド ................................................................................................................................... 142
4.14. COLD から HOT へ変換する拡張メソッド ..................................................................................................... 143
4.14.1. Publish メソッド .................................................................................................................................. 143
4.14.2. RefCount メソッド ............................................................................................................................... 148
4.14.3. 引数を受け取る Publish メソッドのオーバーロード ........................................................................... 151
4.14.4. PublishLast メソッド ........................................................................................................................... 152
4.14.5. Replay メソッド.................................................................................................................................... 153
4.14.6. Multicast メソッド ............................................................................................................................... 155
5. SUBJECT 系クラス ........................................................................................................................................ 159
5.1. SUBJECT<T>クラス ...................................................................................................................................... 159
5.2. BEHAVIORSUBJECT<T>クラス...................................................................................................................... 161
5.3. ASYNCSUBJECT<T>クラス ........................................................................................................................... 162
5.4. REPLAYSUBJECT<T>クラス .......................................................................................................................... 164
6. IOBSERVABLE の合成 .................................................................................................................................. 165
6.1. MERGE メソッド ........................................................................................................................................... 165
6.2. SELECTMANY メソッド ................................................................................................................................. 169
6.3. SWITCH メソッド........................................................................................................................................... 172
6.4. CONCAT メソッド .......................................................................................................................................... 176
6.5. ZIP メソッド .................................................................................................................................................. 178
6.6. AMB メソッド ................................................................................................................................................ 179
6.7. COMBINELATEST メソッド ............................................................................................................................ 180
6.8. STARTWITH メソッド .................................................................................................................................... 182
6.9. JOIN メソッド ............................................................................................................................................... 183
6.10. GROUPJOIN メソッド .................................................................................................................................... 186
6.11. WHEN メソッド ............................................................................................................................................. 189
6.11.1. Plan<TResult>クラスの作成 ............................................................................................................... 189
iv
- 7. 6.11.2. When メソッドの使用例 ....................................................................................................................... 190
6.11.3. まとめ .................................................................................................................................................... 193
7. SCHEDULER ................................................................................................................................................ 193
7.1. 実行場所の切り替え ...................................................................................................................................... 194
7.2. IOBSERVABLE<T>生成時の SCHEDULER の指定方法 ................................................................................... 195
7.2.1. デフォルトの Scheduler ....................................................................................................................... 197
7.3. SCHEDULER の切り替え ................................................................................................................................ 197
7.3.1. ObserveOn メソッド ............................................................................................................................ 197
7.3.2. ObserveOn の使用用途 ......................................................................................................................... 198
7.4. 時間を制御する SCHEDULER ......................................................................................................................... 199
7.4.1. HistoricalScheduler クラス ................................................................................................................. 199
8. 応用編 ............................................................................................................................................................. 200
8.1. センサー監視 ................................................................................................................................................. 200
9. 参考サイト ...................................................................................................................................................... 203
v
- 8. 1. はじめに
ここでは、Reactive Extensions(下記リンク)の著者自身の理解を深めるために著者自身の
Reactive Extensions の理解している内容を記載します。
Dev Lab Reactive Extensions ホームページ
1.1. 前提環境
ここでは、下記の環境を前提に説明を行います。
Visual Studio 2010 SP1 Ultimate
.NET Framework 4
Reactive Extensions 1.0.*
Visual Studio は、Visual C# 2010 SP1 Express Edition でも動作可能です。
1.2. 対象読者
Reactive Extensions によるプログラミングに興味がある方。
1.3. 謝辞
本ドキュメントのもとになった著者の Blog エントリの記事に対してコメント等で指摘していただ
いた方々に感謝いたします。特に@neuecc さんには、サトの情報や私の理解不足による誤った
記載など丁寧に対応していただきとても感謝しています。
1
- 9. 2. Reactive Extensions とは
Reactive Extensions は、公式ページに下記のように説明があります。
The Reactive Extensions (Rx)...
...is a library to compose asynchronous and event-based programs using
observable collections and LINQ-style query operators.
私の拙い英語力で和訳を行うと「Reactive Extensions は、監視可能なコレクションと LINQ スタ
ルのオペレーションを使用して非同期とベントベースのプログラムを合成するラブラリで
す。」となります。
個人的な解釈としては、Reactive Extensions とは何かしらの値を 0 回以上通知するもの(C#の
event や非同期処理やタマーなど etc…)を統一的なプログラミングモデルで扱えるようにした
ものです。 そして、この統一的なプログラミングモデルを提供するための要となるンターフェー
スが System 名前空間の下にある IObservable<T>と IObserver<T>です。
2.1. Observer パターン
Observer と Observable という名前からもわかる通り、このンターフェースはデザンパター
ンの Observer パターンのための機構を提供します。IObserver<T>ンターフェースが、
IObservable<T>ンターフェースの発行するベントを監視するという構造になります。
IObserver<T>ンターフェースには下記の 3 つのメソッドが定義されています。
1. void OnNext(T value)メソッド
IObservable<T>から発生した通知を受け取って処理を行います
2. void OnError(Exception ex)メソッド
IObservable<T>で発生した例外を受け取って処理を行います。
3. void OnCompleted()メソッド
IObservable<T>からの通知が終了した時の処理を行います。
対になる IObservable<T>には下記の 1 つのメソッドが定義されています。
1. IDisposable Subscribe(IObserver<T> observer)メソッド
引数で受け取った Observer にベントの通知を行うようにします。戻り値の IDisposable の
Dispose メソッドを呼び出すと通知を取り消します。
Reactive Extensions は、この Observer パターンを土台にして構築されたラブラリになります。
2.2. Reactive Extensions で提供されている機能
Reactive Extensions は、この IObservable<T>と IObserver<T>をベースに下記のような機能
を提供しています。
1. IObservable<T>のフゔクトリメソッド
Reactive Extensions には IObservable<T>を返すフゔクトリメソッドが多数用意されてい
ます。 .NET の標準のベントから IObservable<T>を生成するメソッドや、 非同期呼び出し、
2
- 10. タマー、シーケンス、特定のルールで生成される値の集合 etc…さまざまなものが提供され
ています。
2. IObservable<T>の拡張メソッド
IObservable<T>と IObserver<T>だけではベントの発行と購読の関係にしかなりません。
Reactive Extensions では、ここに LINQ スタルの拡張メソッドを提供することで
IObservable<T>から発行された値をフゖルタリングしたり、 発行された値を元に別の処理を
行ったり、発行された値の変換を行ったりすることが出来ます。
IObserver<T>生成へのショートカット
IObserver<T>を実装しなくても、ラムダ式から IObserver<T>を内部的に生成してくれるため
実際に Reactive Extensions を使用するときには IObserver<T>ンターフェースを実装するケ
ースは、ほとんどありません。
3. 柔軟なスレッドの切り替え機能
IObservable<T>から発行された値に対する処理を何処のスレッドで実行するのか柔軟に切
り替える機能が提供されています。このためバックグラウンドで時間のかかる処理を行い、UI
スレッドに切り替えて画面の更新を行うといった処理が簡単に行えるようになります。
以上が、Reactive Extensions で提供されている機能の全体像になります。次からは、Reactive
Extensions の基本となる IObservable<T>ンターフェースと IObserver<T>ンターフェー
スを見ていこうと思います。
2.3. IObservalbe<T>インターフェースと IObserver<T>インターフェース
ここでは、Reactive Extensions の機能の要となる IObservable<T>と IObserver<T>を実装し
て、その動作を確認します。まずは、Observer パターンでの監視役となる IObserver<T>から実
装を行います。IObserver<T>ンターフェースは、先に示したように OnNext と OnError と
OnCompleted の3つのメソッドからなります。この3種類のメソッド内に IObservable<T>か
ら通知された値を受け取った時の処理を行います。ここでは、IObservable<T>から通知された
値を単純にコンソールに出力するものを作成します。
namespace IObservableIObserverImpl
{
using System;
// 監視する人
class PrintObserver : IObserver<int>
{
// 監視対象から通知が来たときの処理
public void OnNext(int value)
{
Console.WriteLine("OnNext({0}) called.", value);
}
3
- 11. // 完了通知が来たときの処理
public void OnCompleted()
{
Console.WriteLine("OnCompleted called.");
}
// エラー通知が来たときの処理
public void OnError(Exception error)
{
Console.WriteLine("OnError({0}) called.", error.Message);
}
}
}
単純に、OnNext と OnCompleted と OnError で標準出力に値を出力しています。次に
IObservable<T>を実装したコードを示します。
namespace IObservableIObserverImpl
{
using System;
using System.Collections.Generic;
/// <summary>
/// 監視されるクラス
/// </summary>
class NumberObservable : IObservable<int>
{
// 自分を監視してる人を管理するリスト
private List<IObserver<int>> observers = new List<IObserver<int>>();
// 自分を監視してる人に通知を行う
// 0 を渡したらエラー通知
public void Execute(int value)
{
if (value == 0)
{
foreach (var obs in observers)
{
obs.OnError(new Exception("value is 0"));
4
- 12. }
// エラーが起きたので処理は終了
this.observers.Clear();
return;
}
foreach (var obs in observers)
{
obs.OnNext(value);
}
}
// 完了通知
public void Completed()
{
foreach (var obs in observers)
{
obs.OnCompleted();
}
// 完了したので監視してる人たちをクリゕ
this.observers.Clear();
}
// 監視してる人を追加する。
// 戻り値の IDisposable を Dispose すると監視から外れる。
public IDisposable Subscribe(IObserver<int> observer)
{
this.observers.Add(observer);
return new RemoveListDisposable(observers, observer);
}
// Dispose が呼ばれたら observer を監視対象から削除する
private class RemoveListDisposable : IDisposable
{
private List<IObserver<int>> observers = new List<IObserver<int>>();
private IObserver<int> observer;
5
- 13. public RemoveListDisposable(List<IObserver<int>> observers, IObserver<int> observer)
{
this.observers = observers;
this.observer = observer;
}
public void Dispose()
{
if (this.observers == null)
{
return;
}
if (observers.IndexOf(observer) != -1)
{
this.observers.Remove(observer);
}
this.observers = null;
this.observer = null;
}
}
}
}
IObservable<T>の実装は IObserver<T>に比べて複雑になっています。これは、
IObservable<T>ンターフェースが IObserver<T>を自分自身の監視役として登録する
Subscribe メソッドしか提供していないため、その他の監視役の IObserver<T>の保持や、
Subscribe メソッドの戻り値の IDosposable の Dispose を呼び出したときの監視役解除の処理を
作りこんでいるためです。どちらも一般的な C#によるプログラミングの範囲の内容になるので詳
細は割愛します。 最期に、この PrintObserver クラスと NumberObservable クラスを使ったサン
プルプログラムを以下に示します。
namespace IObservableIObserverImpl
{
using System;
class Program
6
- 14. {
static void Main(string[] args)
{
// 監視される人を作成
var source = new NumberObservable();
// 監視役を2つ登録
var sbscriber1 = source.Subscribe(new PrintObserver());
var sbscriber2 = source.Subscribe(new PrintObserver());
// 監視される人の処理を実行
Console.WriteLine("## Execute(1)");
source.Execute(1);
// 片方を監視する人から解雇
Console.WriteLine("## Dispose");
sbscriber2.Dispose();
// 再度処理を実行
Console.WriteLine("## Execute(2)");
source.Execute(2);
// エラーを起こしてみる
Console.WriteLine("## Execute(0)");
source.Execute(0);
// 完了通知
// もう 1 つ監視役を追加して完了通知を行う
var sbscriber3 = source.Subscribe(new PrintObserver());
Console.WriteLine("## Completed");
source.Completed();
}
}
}
上記のプログラムは、NumberObservable 型の変数 source に PrintObserver を 2 つ登録してい
ます。その状態で Execute メソッドを呼んだり Dispose を読んだりエラーを起こしたりして出力
を確認しています。このプログラムを動かすと下記のような結果になります。
## Execute(1)
OnNext(1) called.
OnNext(1) called.
## Dispose
7
- 15. ## Execute(2)
OnNext(2) called.
## Execute(0)
OnError(value is 0) called.
## Completed
OnCompleted called.
最初の Execute では、PrintObserver を 2 つ登録しているので 2 回 OnNext が呼ばれていること
が確認出来ます。 次に片方を Dispose した後では Execute を読んでも 1 回しか OnNext が呼ばれ
ません。 Execute メソッドの引数に 0 を渡してエラーを起こした場合と処理を完了させたときも、
PrintObserver に処理が伝わっていることが確認できます。
2.4. Reactive Extensions の機能を使った書き直し
ここまで書いてきたプログラムは、Reactive Extensions の説明というよりは IObservable<T>
ンターフェースと IObserver<T>ンターフェースを実装して使用しただけの Observer パタ
ーンの1実装例です。ここでは、このプログラムを Reactive Extensions が提供する便利な拡張
メソッドやクラスを使って書き換えを行います。
Reactive Extensions を使ったプログラムでは、頻繁に IObservable<T>ンターフェースの
Subscribe メソッドを使用して通知に対するゕクションを設定します。この時、実行したい処理の
単位でクラスを作成するのは現実的ではありません。そのため、Reactive Extensions では
System.ObservableExtensions クラスで IObservable<T>ンターフェースの拡張メソッドを
定義しています。主な拡張メソッドは下記の 3 つになります。どれもデリゲートを受け取るタ
プになります。
void Subscribe<T>(this IObservable<T> source, Action<T> onNext)
void Subscribe<T>( this IObservable<T> source, Action<T> onNext, Action
onCompleted)
void Subscribe<T>( this IObservable<T> source, Action<T> onNext,
Action<Exception> onError, Action onCompleted)
この拡張メソッドを使うことでデリゲートで IObserver<T>ンターフェースの OnNext メソッ
ドと OnError メソッドと OnCompleted メソッドを指定するだけで内部で IObserver<T>を継承
したクラスを作成して IObservable<T>ンターフェースの Subscribe(IObserver<T>)メソッ
ドへ渡してくれます。このため、Reactive Extensions を使う上では IObserver<T>ンターフ
ェースを実装することは、ほぼ無くなります。 (私は今まで実際の処理を書いていて実装したこと
は有りません)上記の拡張メソッドを使うためには Reactive Extensions をプロジェクトの参照
に追加します。 Nuget で Rx-Main という名前のパッケージをプロジェクトに追加します。 (Express
Edition の方は nuget コマンドランをンストールして nuget install rx-main でダウンロード
されるので手動で参照に追加してください)そして、 Main のプログラムを下記のように変更します。
// 監視される人を作成
var source = new NumberObservable();
// 2 つ監視役を登録
8
- 16. var subscriber1 = source.Subscribe(
// OnNext
value => Console.WriteLine("OnNext({0}) called.", value),
// OnError
ex => Console.WriteLine("OnError({0}) called.", ex.Message),
// OnCompleted
() => Console.WriteLine("OnCompleted() called."));
var subscriber2 = source.Subscribe(
// OnNext
value => Console.WriteLine("OnNext({0}) called.", value),
// OnError
ex => Console.WriteLine("OnError({0}) called.", ex.Message),
// OnCompleted
() => Console.WriteLine("OnCompleted() called."));
// 監視される人の処理を実行
Console.WriteLine("## Execute(1)");
source.Execute(1);
// 1 つを監視する人から解雇
Console.WriteLine("## Dispose");
subscriber2.Dispose();
// 再度処理を実行
Console.WriteLine("## Execute(2)");
source.Execute(2);
// エラーを起こしてみる
Console.WriteLine("## Execute(0)");
source.Execute(0);
// もう 1 つ監視役を追加して完了通知を行う
var sbscriber3 = source.Subscribe(
// OnNext
value => Console.WriteLine("OnNext({0}) called.", value),
// OnError
ex => Console.WriteLine("OnError({0}) called.", ex.Message),
// OnCompleted
() => Console.WriteLine("OnCompleted() called."));
Console.WriteLine("## Completed");
source.Completed();
9
- 17. 引数を 3 つ受け取るタプの Subscribe 拡張メソッドを使用しているため PrintObserver クラス
は不要になります。今回の例では、同じ処理を何度も Subscribe しているのでメソッドとして切
り出すなりしたほうがコードの重複が無くなりますが、 サンプルとしての見通しのためにあえて 1
メソッド内に重複コードを書いています。
次に IObservable<T>の実装クラスですが、これも IObservable<T>を実装したクラスが提供さ
れています。System.Reactive.Subjects.Subject<T>クラスがそれにあたります。このクラスは
IObservable<T>ンターフェースと IObserver<T>ンターフェースの両方を実装していて
OnNext や OnError や OnCompleted などの IObserver<T>ンターフェースで提供されている
メソッドを呼び出すと IObservable<T>ンターフェースの Subscribe メソッドで監視対象とし
て追加されている IObserver<T>に処理を通知します。このクラスをラップする形で使うと簡単
に IObservable<T>ンターフェースを実装することが出来ます。
namespace UseSubscribeMethod
{
using System;
using System.Reactive.Subjects;
class NumberObservable : IObservable<int>
{
// IObservable<T>と IObserver<T>の両方を兼ねるクラス
private Subject<int> source = new Subject<int>();
// 自分を監視してる人に通知を行う
// 0 を渡したらエラー通知
public void Execute(int value)
{
if (value == 0)
{
this.source.OnError(new Exception("value is 0"));
// エラー状態じゃないまっさらな Subject を再作成
this.source = new Subject<int>();
return;
}
this.source.OnNext(value);
}
// 完了通知
public void Completed()
10
- 18. {
this.source.OnCompleted();
}
// 監視してる人を追加する。
// 戻り値の IDisposable を Dispose すると監視から外れる。
public IDisposable Subscribe(IObserver<int> observer)
{
return this.source.Subscribe(observer);
}
}
}
これで、初期のプログラムと同じ動作を実装出来ました。ただし、Reactive Extensions を普通に
使っている範囲では、 Subject<T>クラスは使用することは少ないです。本来は、IObservable<T>
を実装したクラスを生成するためのフゔクトリメソッドが用意されているので、 そちらを利用する
ことのほうが多いです。 しかし、 動作確認をするためには自分で値の発行などが細かく制御できる
Subject<T>クラスを、これから先のサンプルで使用するために紹介しました。
3. IObservable<T>のファクトリメソッド
ここでは、Reactive Extensions で提供される IObservable<T>を作成するフゔクトリメソッド
を紹介します。通常の Reactive Extensions を使ったプログラミングでは、提供されている様々
なフゔクトリメソッドから IObservable<T>を生成して使用します。そのため、どのような
IObservable<T>の生成方法があるかを把握していることは Reactive Extensions を使う上でと
ても重要な要素になります。フゔクトリメソッドは基本的に System.Reactive.Linq.Observable
クラスの static メソッドとして提供されています。メソッドの一覧は下記の MSDN のドキュメン
トを参照してください。
MSDN Observable クラス
http://msdn.microsoft.com/en-us/library/system.reactive.linq.observable(v=VS.103).as
px
3.1. 基本的なファクトリメソッドの使用
ここでは、数多くある Observable クラスのフゔクトリメソッドから基本的なものをいくつか紹介
します。
11
- 19. 3.2. 指定された値を返す IObservable<T>を返すファクトリメソッド
3.2.1. Observable.Return メソッド
まず、一番動作を理解しやすい指定した値を通知する IObservable<T>を作成するメソッドから
使用します。最初に紹介するメソッドは Return メソッドになります。これは引数に指定した値を
通知する IObservable<T>を作成します。コード例を以下に示します。
// 10 を発行する IObservable<int>を作成する
var source = Observable.Return(10);
// 購読
var subscription = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
// 購読の停止(この場合意味はない)
subscription.Dispose();
コメントにあるように、このコードは 10 という値を発行する IObservable<int>を作成していま
す。実行結果は下記のようになります。
OnNext(10)
Completed()
Return で作成した IObservable<T>は値を 1 つ発行すると、それ以降発行する値が無いため終了
状態になります。そのため Subscribe をすると実行結果のように OnNext の後に Completed が
呼ばれます。また、ここでは示していませんが 2 回 Subscribe を行うと値の発行と終了の通知を
再び行います。上記のコード例で言うと、2 回目の Subscribe の呼び出しでも、OnNext(10)と
Completed()が表示されます。
3.2.2. Observable.Repeat メソッド
次は、同じ値を指定した回数発行する IObservable<T>を返すメソッドになります。コード例を
下記に示します。
// 2 を 5 回発行する IObservable<int>を作成する
var source = Observable.Repeat(2, 5);
// 購読
var subscription = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
// 購読停止(この場合意味はない)
subscription.Dispose();
12
- 20. これを実行すると下記のようになります。
OnNext(2)
OnNext(2)
OnNext(2)
OnNext(2)
OnNext(2)
Completed()
同じ値をひたすら発行しているのがわかります。
3.2.3. Observable.Range メソッド
このメソッドは、指定した値から 1 ずつカウントゕップした値を指定した個数だけ返します。コ
ード例を下記に示します。
// 1 から始まる値を 10 個発行する IObservable<int>を作成する
var source = Observable.Range(1, 10);
// 購読
var subscription = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
// 購読停止(この場合意味はない)
subscription.Dispose();
1 からカウントゕップする値を 10 個発行するので 1~10 の値を発行する IObservable<int>を作
成しています。実行例を下記に示します。
OnNext(1)
OnNext(2)
OnNext(3)
OnNext(4)
OnNext(5)
OnNext(6)
OnNext(7)
OnNext(8)
OnNext(9)
OnNext(10)
Completed()
13
- 21. 3.2.3.1. IObservable<T>の拡張メソッドの Repeat
ここまでは Observable クラスに定義された IObservable<T>を返すメソッドを使ってきました
が、ここで少し横道に逸れて IObservable<T>の拡張メソッドとして定義された Repeat メソッ
ドを紹介したいと思います。この拡張メソッドも Observable クラスに定義されています。これま
でのメソッドとの違いは純粋な static メソッドではなく、IObservable<T>の拡張メソッドとし
て定義されている点です。 この Repeat 拡張メソッドは、 IObservable<T>が発行する値を指定し
た回数繰り返す IObservable<T>を作成します。Range メソッドと組み合わせて使用した例を下
記に示します。
// 1 から始まる値を 3 個発行する IObservable<int>を作成する
var source = Observable.Range(1, 3);
// そして、それを 3 回繰り返す IObservable<int>を作成する
source = source.Repeat(3);
// 購読
var subscription = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
// 購読停止(この場合意味はない)
subscription.Dispose();
実行結果は下記のようになります。1~3 の値が 3 回発行されているのがわかります。
OnNext(1)
OnNext(2)
OnNext(3)
OnNext(1)
OnNext(2)
OnNext(3)
OnNext(1)
OnNext(2)
OnNext(3)
Completed()
3.2.4. Observable.Generate メソッド
次は、Generate メソッドです。このメソッドは for 文に近い使用感のメソッドになっています。
第一引数で初期値状態、第二引数で継続の条件、第三引数で更新処理、第四引数で発行する値の生
成を行う処理を渡します。 コードを見ていただくとメージがわきやすいと思うので下記にコード
例を示します。
// 初期値 0, 値が 10 より小さい間, 値は 1 ずつンクリメントして, 値を二乗したものを発行する
14
- 22. // IObservable<int>を作成する。
// for (int i = 0; i < 10; i++) { yield return i * i; }のようなメージ
var source = Observable.Generate(0, i => i < 10, i => ++i, i => i * i);
// 購読
var subscription = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
// 購読停止(この場合意味はない)
subscription.Dispose();
このコードを実行すると 0~9 までの値を二乗したものを発行する IObservable<int>が作成され
ます。実行結果は下記のようになります。
OnNext(0)
OnNext(1)
OnNext(4)
OnNext(9)
OnNext(16)
OnNext(25)
OnNext(36)
OnNext(49)
OnNext(64)
OnNext(81)
Completed()
0~9 までの値を二乗した値が発行されているのが確認出来ます。
3.2.5. Observable.Defer メソッド
次に紹介するメソッドは Defer メソッドです。このメソッドは IObservable<T>を直接返すラム
ダ式を引数に渡します。Subscribe メソッドが呼ばれる度に、Defer メソッドが実行されて
IObservable<T>が作成されます。コード例を以下に示します。
// 1, 2, 3 と順番に値を発行して終了する IObservable<int>を生成する
var source = Observable.Defer<int>(() =>
{
Console.WriteLine("# Defar method called.");
// ReplaySubject<T>は Subject<T>の亜種で Subscribe されると
// 今まで行われた操作を全てリプレする。
var s = new ReplaySubject<int>();
s.OnNext(1);
15
- 23. s.OnNext(2);
s.OnNext(3);
s.OnCompleted();
// AsObservable で IObservable<T>へ変換できる。
return s.AsObservable();
});
// 購読(source は ReplaySubject で作っているので Defer メソッド内でした操作が再生される)
var subscription1 = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
var subscription2 = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
// 購読停止(この場合意味はない)
subscription1.Dispose();
subscription2.Dispose();
実行結果は下記のようになります。2 回 Subscribe しているので 2 回 Defer メソッドが呼ばれて
いることが確認出来ます。
# Defar method called.
OnNext(1)
OnNext(2)
OnNext(3)
Completed()
# Defar method called.
OnNext(1)
OnNext(2)
OnNext(3)
Completed()
Defer メソッド内で使用している ReplaySubject<T>クラスは、コメント内にあるように
Subject<T>クラスの親戚のクラスです。 Subject<T>クラスが Subscribe される前の操作は通知
しないのに対して ReplaySubject<T>クラスは、Subscribe される前の操作も通知する点が異な
ります。試しに Defer メソッド内の ReplaySubject<T>を普通の Subject<T>に変更して実行す
ると下記のような結果になります。
# Defar method called.
Completed()
16
- 24. # Defar method called.
Completed()
Defer メソッドで返された IObservable<int>は既に通知する値が無く完了した状態になってい
るため、OnCompleted だけが実行されます。
3.2.6. Observable.Create メソッド
次は、Create メソッドを紹介します。このメソッドは、引数の形が特殊で IObserver<T>を受け
取って Action を返すラムダ式を引数に受け取ります。引数で受け取る IObserver<T>には
OnNext や OnErrror や OnCompleted などの操作を行います。ここで行った操作に応じた値が
Create メソッドの戻り値の IObservable<T>で発行される値になります。 最期に戻り値の Action
ですが、これは Dispose された時に実行される処理になります。Create メソッド内でリソースや
ベントの購読などをしていた場合に解放する処理を行うと良いと思います。 では、コード例を下
記に示します。
// 1, 2, 3 と順番に値を発行して終了する IObservable<int>を生成する
var source = Observable.Create<int>(observer =>
{
Console.WriteLine("# Create method called.");
// 引数の IObserver<int>に対して On****メソッドを呼ぶ
observer.OnNext(1);
observer.OnNext(2);
observer.OnNext(3);
observer.OnCompleted();
// Dispose が呼ばれた時の処理を返す。
// リソースを確保していた場合は、ここで解放すると良い。
return () => Console.WriteLine("Disposable action");
});
// 購読
var subscription1 = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
var subscription2 = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
// 購読停止(この場合意味はない)
Console.WriteLine("# Dispose method call.");
subscription1.Dispose();
17
- 25. subscription2.Dispose();
実行結果は、下記のようになります。
## CreateSample
# Create method called.
OnNext(1)
OnNext(2)
OnNext(3)
Completed()
Disposable action
# Create method called.
OnNext(1)
OnNext(2)
OnNext(3)
Completed()
Disposable action
# Dispose method call.
注意したいのは Disposable action の表示されているタミングです。通常の考えだと
subscription1.Dispose();の呼び出しタミングで Disposable action と表示されるように思いま
すが動作を確認すると Dispose メソッドを呼ぶ前に自動で呼び出されています。これは、
Completed の後で、もう購読していても値が来ないため Dispose が自動で行われていることを示
しています。 これまでのサンプルでも Dispose に特に意味がないと書いていたのはこのためです。
3.2.7. Observable.Throw メソッド
一連の基本的な IObservable<T>を作成するメソッドの締めくくりとして最後に Throw メソッド
を紹介します。これは引数に例外を渡します。 疑似的にエラーを起こしたいときに使う以外に使用
方法が思いつきません。コード例を下記に示します。
// エラーを発行するだけの IObservable<int>を生成
var source = Observable.Throw<int>(new Exception("Error message"));
// 購読
var subscription = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
// 購読停止(この場合意味はない)
subscription.Dispose();
実行結果は下記のようになります。OnError が呼ばれているのがわかります。
OnError(Error message)
18
- 26. 3.3. ここまでに紹介した IObservable<T>の動作
ここまで紹介してきた IObservable<T>は、全て下記のような特徴があります。
1. フゔクトリで発行する値を指定して IObservable<T>を作成する。
2. Subscribe が呼び出されると以下のような動きをする
1. 値を全て発行する
2. 終わったら OnCompleted を呼ぶ
3. 購読を解除する
3. Subscribe が行われる度に 2 の動作を行う。
この動作を表した図を以下に示します。
上記の図では OnError と二度目の Subscribe について書いていませんが、 基本的に OnError の時
には例外が IObserver<T>に通知されます。また、一度の Subscribe で IObservable<T>が空に
なるように見えますがデータの流れを示すために空にしているだけで実際には再度 Subscribe を
行うと値が発行されます。
3.4. リソースの確保を伴う IObservable<T>
ここでは、IObservable<T>を取得する際にリソースの確保を行うケースに使用できるフゔクト
リメソッドについて説明します。
19
- 27. 3.4.1. Observable.Uting メソッド
Using メソッドは、 名前の通り using ブロックのようにリソースを確実に解放するために使用する
メソッドになります。メソッドのシグネチャは、第一引数に Func<TResource>を渡して、リソ
ースを確保するオブジェクトを作成する処理を指定します。TResource 型は IDisposable を実装
している必要があります。 第二引数に、 Func<TResource, IObservable<TSource>>を渡して、
リソースを使って IObservable<T>を取得する処理を指定します。実際には、外部リソースに依
存するものを使用する場合のコード例が適しているのですが、ここでのサンプルは、ダミーの
IDisposable を実装した下記のようなクラスを使用して動作確認を行います。
// サンプルのダミーリソースクラス
class SampleResource : IDisposable
{
// データを取得する
public IObservable<string> GetData()
{
return Observable.Create<string>(o =>
{
o.OnNext("one");
o.OnNext("two");
o.OnNext("three");
o.OnCompleted();
return Disposable.Empty;
});
}
// 解放処理
public void Dispose()
{
Console.WriteLine("Resource.Dispose called");
}
}
このクラスを使用して Using メソッドの動作確認を行います。Using メソッドの使用例を下記に
示します。
// SampleResource(IDisposable を実装)を使用してデータを取得する
var source = Observable.Using(
() => new SampleResource(),
sr => sr.GetData());
// 購読
20
- 28. source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
Using メソッドで SampleResource クラスの作成と、SampleResource クラスを使って
IObservable<T>を取得しています。このコードの実行結果を下記に示します。
OnNext(one)
OnNext(two)
OnNext(three)
Completed()
Resource.Dispose called
実行結果からもわかるように、最後に、SampleResource クラスの Dispose が呼ばれていること
がわかります。
3.5. 時間と共に値を発行する IObservable<T>
ここでは時間と共に値を発行する IObservable<T>を返すフゔクトリメソッドを使って動作を確
認します。
3.5.1. Observable.Timer メソッド
まず、一番直感的に理解に理解できると思われる Timer メソッドを使用します。Timer メソッド
は名前の通り一定時間ごとに値を発行します。 発行する値は Timer が実行された回数になります。
いくつかオーバーロードがありますが、 第一引数にタマーを開始するまでの時間、 第二引数にタ
マーのンターバルを TimeSpan 型で指定するオーバーロードが、一番使用頻度が高いと思い
ます。コード例を下記に示します。
// 3 秒後から 1 秒間隔で値を発行する IObservable<long>を作成する
var source = Observable.Timer(
TimeSpan.FromSeconds(3),
TimeSpan.FromSeconds(1));
// 購読
var subscription = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
// 3 秒後から OnNext(回数)が表示される
Console.WriteLine("please enter key...");
Console.ReadLine();
21
- 29. // Observable が発行する値の購読を停止
Console.WriteLine("dispose method call.");
subscription.Dispose();
// Dispose をすると値が発行されなくなる。
Console.WriteLine("please enter key...");
Console.ReadLine();
コメントにもある通り、 上記のコードは 3 秒後から 1 秒間隔で値を発行する IObservable<long>
を作成して Subscribe で購読しています。そして、Console.ReadLine で待機しています。暫く
待っていると OnNext が発行されます。適当なタミングで Enter キーを押すと
subscription.Dispose()が実行され、購読が解除されます。Dispose のあとは、OnNext が実行さ
れないことを確認できます。実行結果を下記に示します。
please enter key... // ここから 3 秒何も表示されない
OnNext(0) // 3 秒たつと 1 秒間隔で OnNext が呼ばれる
OnNext(1)
OnNext(2)
OnNext(3)
OnNext(4)
OnNext(5)
dispose method call. // Enter を押して Dispose が呼ばれると OnNext も止まる
please enter key...
Timer の実行メージを下図に示します。
22
- 30. 3.5.2. Observable.Interval メソッド
次も Timer メソッドと同じように使用できるメソッドを紹介します。こちらは Interval メソッド
で TimeSpan を 1 つ渡すだけでシンプルに使用できます。Subscribe した時点から TimeSpan で
指定した間隔で値を発行します。発行する値は Timer と同じで何回値を発行したかという数字に
なります。コード例を下記に示します。
// 500ms 間隔で値を発行する
var source = Observable.Interval(TimeSpan.FromMilliseconds(500));
// 購読
var subscription = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
Console.WriteLine("please enter key...");
Console.ReadLine();
// Observable が発行する値の購読を停止
Console.WriteLine("dispose method call.");
subscription.Dispose();
// Dispose をすると値が発行されても受け取らなくなる。
Console.WriteLine("please enter key...");
Console.ReadLine();
上記の例では 500ms 間隔で値を発行しています。Enter キーを押すと Dispose を読んで購読を停
止して再度 Enter キーが押されるまで待機します。実行結果を下記に示します。
please enter key...
OnNext(0)
OnNext(1)
OnNext(2)
OnNext(3)
OnNext(4)
OnNext(5)
OnNext(6)
dispose method call.
please enter key...
この実行結果では 6 が表示されたタミングで Enter キーを押して Dispose を実行しています。
実行結果からは読み取れませんが、上記の実行結果は Dispose が実行されたあとに数秒間待機し
て購読が停止していることを確認しています。
23
- 31. 3.5.3. Observable.Generate メソッド
次は、3.2.4 でも使用した Generate メソッドを使用します。Generate メソッドには TimeSpan
を受け取るオーバーロードがあり、これを使うことで指定した時間間隔で値を発行する
IObservable<T>を作成できます。コード例を下記に示します。
var source = Observable.Generate(
// 0 から
0,
// i < 10 以下の間繰り返す
i => i < 10,
// i は 1 ずつ増える
i => ++i,
// 発行する値は i の二乗
i => i * i,
// 値は(発行する値 * 100)ms 間隔で発行する
i => TimeSpan.FromMilliseconds(i * 100));
// 購読
var subscription = source.Subscribe(
i => Console.WriteLine("OnNext({0})", i),
ex => Console.WriteLine("OnError({0})", ex.Message),
() => Console.WriteLine("Completed()"));
Console.WriteLine("please enter key...");
Console.ReadLine();
// Observable が発行する値の購読を停止
Console.WriteLine("dispose method call.");
subscription.Dispose();
// Dispose をすると値が発行されても受け取らなくなる。
Console.WriteLine("please enter key...");
Console.ReadLine();
上記の例は(0 * 0 * 100)ms, (1 * 1 * 100)ms, (2 * 2 * 100)ms, (3 * 3 * 100)ms…(9 * 9 *
100)ms と値の発行回数増える度にンターバルを長くとるようにしています。 実行結果を下記に
示します。
please enter key...
OnNext(0)
OnNext(1)
OnNext(4)
24
- 32. OnNext(9)
OnNext(16)
OnNext(25)
OnNext(36)
OnNext(49)
OnNext(64)
OnNext(81)
Completed()
dispose method call.
please enter key...
上記実行例は、最後まで Enter キーを押さずに実行した場合になります。途中で Enter キーを押
した場合の実行例を下記に示します。
please enter key...
OnNext(0)
OnNext(1)
OnNext(4)
OnNext(9)
OnNext(16)
dispose method call.
please enter key...
このように Dispose を呼ぶと Generate メソッドの処理を途中から購読しなくなります。
3.6. Cold な IObservable<T>と Hot な IObservable<T>
ここまでに紹介した IObservable<T>のフゔクトリメソッドは全て共通の特徴があります。それ
は複数回 Subscribe すると、それぞれの IObserver<T>に IObservable<T>が個別に値を発行し
ます。IObservable<T>の作り方にもよりますが、基本的には同じ値が発行されていきます。文
章で説明するよりもコードの動作例で示します。
// 1 秒間隔で値を発行する IObservable<long>を作成する
var source = Observable.Timer(
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(1));
// 購読
var subscription1 = source.Subscribe(
i => Console.WriteLine("{0:yyyy/MM/dd HH:mm:ss.FFF} 1##OnNext({1})", DateTime.Now, i),
ex => Console.WriteLine("1##OnError({0})", ex.Message),
25
- 33. () => Console.WriteLine("1##Completed()"));
// 3 秒後にもう一度購読
Thread.Sleep(3000);
// 購読
var subscription2 = source.Subscribe(
i => Console.WriteLine("{0:yyyy/MM/dd HH:mm:ss.FFF} 2##OnNext({1})", DateTime.Now, i),
ex => Console.WriteLine("2##OnError({0})", ex.Message),
() => Console.WriteLine("2##Completed()"));
Console.ReadLine();
subscription1.Dispose();
subscription2.Dispose();
上記のコードは 1 秒間隔で値を発行する IObservable<long>を作成し、 回 Subscribe したあと
1
に 3 秒待ってもう一度 Subscribe しています。この実行結果を下記に示します。
2011/11/07 23:05:01.883 1##OnNext(0)
2011/11/07 23:05:02.884 1##OnNext(1)
2011/11/07 23:05:03.879 1##OnNext(2)
2011/11/07 23:05:04.877 1##OnNext(3)
2011/11/07 23:05:04.893 2##OnNext(0)
2011/11/07 23:05:05.89 1##OnNext(4)
2011/11/07 23:05:05.894 2##OnNext(1)
2011/11/07 23:05:06.877 1##OnNext(5)
2011/11/07 23:05:06.892 2##OnNext(2)
2011/11/07 23:05:07.873 1##OnNext(6)
2011/11/07 23:05:07.894 2##OnNext(3)
最初に Subscribe した方には yyyy/MM/dd HH:mm:ss.FFF 1##OnNext(値)の形式で出力して
います。二番目に Subscribe した方には yyyy/MM/dd HH:mm:ss.FFF 2##OnNext(値)の形式
で出力しています。これを見ると、最初に Subscribe したものと二回目に Subscribe したもので
は、タムスタンプが微妙にずれていることから、 内部的には別のタマーが割り当てられている
ことが見て取れます。また、発行される値も Subscribe した時点で 0 からカウントされていて最
初に Subscribe したものと二回目に Subscribe したものの間に関係性は全くありません。このよ
うに、Cold な Observable は「複数回 Subscribe した時に Observer 毎に独立した値を発行する」
という特徴があります。
では次に、Cold と対となるものとして Hot な IObservable<T>を見ていきます。Hot な
Observable の代表として.NET 組み込みの Observer パターンであるベントから
IObservable<T>を生成する Observable.FromEvent<TDelegate, TEventArgs>(…)というメ
ソッドがあります。このメソッドのシグネチャを下記に示します。
26
- 34. public static IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(
// Action<TEventArgs>からベントハンドラの形へと変換する処理
// Action<TEventArgs>は Subscribe したときの OnNext の処理にあたる。
Func<Action<TEventArgs>, TDelegate> conversion,
// ベントハンドラを登録する処理
Action<TDelegate> addHandler,
// ベントハンドラの登録を解除する処理
Action<TDelegate> removeHandler)
今まで出てきたもののなかではかなり異質の引数ですがベントハンドラの形にあったデリゲー
トを作成してハンドラの登録処理と削除処理を渡します。あとは IObservable<TEventArgs>を
Subscribe したタミングや Dispose したタミングで適切にベントの登録・登録解除が行わ
れます。 自前でやるとベントハンドラの登録解除は忘れがちな処理なので地味に有りがたい機能
です。コード例を下記に示します。
// 1 秒間隔で値を発行する Timer
var timer = new System.Timers.Timer(1000);
var source = Observable.FromEvent<ElapsedEventHandler, ElapsedEventArgs>(
h => (s, e) => h(e),
h => timer.Elapsed += h,
h => timer.Elapsed -= h);
// タマー開始
timer.Start();
// 購読
var subscription1 = source.Subscribe(
e => Console.WriteLine("{0:yyyy/MM/dd HH:mm:ss.FFF} 1##OnNext({1:yyyy/MM/dd
HH:mm:ss.FFF})",DateTime.Now, e.SignalTime),
ex => Console.WriteLine("1##OnError({0})", ex.Message),
() => Console.WriteLine("1##Completed()"));
// 3 秒後にもう一度購読
Thread.Sleep(3000);
// 購読
var subscription2 = source.Subscribe(
e => Console.WriteLine("{0:yyyy/MM/dd HH:mm:ss.FFF} 2##OnNext({1:yyyy/MM/dd
HH:mm:ss.FFF})",DateTime.Now, e.SignalTime),
ex => Console.WriteLine("2##OnError({0})", ex.Message),
() => Console.WriteLine("2##Completed()"));
27
- 35. Console.ReadLine();
subscription1.Dispose();
subscription2.Dispose();
timer.Stop();
Cold な Observable で示した処理と基本的には同じ処理を行っています。実行結果を下記に示し
ます。
2011/11/07 23:37:41.146 1##OnNext(2011/11/07 23:37:41.146)
2011/11/07 23:37:42.16 1##OnNext(2011/11/07 23:37:42.16)
2011/11/07 23:37:43.174 1##OnNext(2011/11/07 23:37:43.174) ← ここと
2011/11/07 23:37:43.174 2##OnNext(2011/11/07 23:37:43.174) ← ここ 以下 2 つずつ同じ値が表示されてい
る
2011/11/07 23:37:44.188 1##OnNext(2011/11/07 23:37:44.188)
2011/11/07 23:37:44.188 2##OnNext(2011/11/07 23:37:44.188)
2011/11/07 23:37:45.202 1##OnNext(2011/11/07 23:37:45.202)
2011/11/07 23:37:45.202 2##OnNext(2011/11/07 23:37:45.202)
2011/11/07 23:37:46.216 1##OnNext(2011/11/07 23:37:46.216)
2011/11/07 23:37:46.216 2##OnNext(2011/11/07 23:37:46.216)
上記の結果で興味深い点は、最初に Subscribe したものと、二番目に Subscribe したものの出力
結果のタムスタンプと、OnNext に渡ってきている値が同じという点です。
つまり、Hot な IObservable<T>とは Cold な IObservable<T>と違って「複数回 Subscribe し
たときに、全ての Observer に同じタミングで同じ値を発行するもの」ということになります。
3.6.1. Observable.FromEvent メソッド
では、Hot な IObservable<T>を作成するメソッドのトップバッターとして先ほど登場した
FromEvent メソッドを紹介します。FromEvent メソッドのシグネチャは既に示したので、より
詳しく動作を確認するためのコードを下記に示します。
// ベントを発行するクラス
var eventSource = new EventSource();
var source = Observable.FromEvent<EventHandler, EventArgs>(
h => (s, e) => h(e),
// 普通は h => eventSource.Raised += h だけでいい
h =>
{
Console.WriteLine("add handler");
eventSource.Raised += h;
},
// 普通は h => eventSource.Raised -= h だけでいい
28
- 36. h =>
{
Console.WriteLine("remove handler");
eventSource.Raised -= h;
});
// 2 回購読
var subscription1 = source.Subscribe(
i => Console.WriteLine("1##OnNext({0})", i),
ex => Console.WriteLine("1##OnError({0})", ex.Message),
() => Console.WriteLine("1##Completed()"));
var subscription2 = source.Subscribe(
i => Console.WriteLine("2##OnNext({0})", i),
ex => Console.WriteLine("2##OnError({0})", ex.Message),
() => Console.WriteLine("2##Completed()"));
// 2 回呼び出してみる
// 合計 4 回の OnNext が呼ばれるはず
eventSource.OnRaised();
eventSource.OnRaised();
// Observable が発行する値の購読を停止
Console.WriteLine("dispose method call.");
subscription1.Dispose();
subscription2.Dispose();
この例で使用している EventSource クラスは、Raised というベントと OnRaised というベ
ントを発行するメソッドだけを持ったクラスで下記のように定義しています。
// ベント発行クラス
class EventSource
{
public event EventHandler Raised;
public void OnRaised()
{
var h = this.Raised;
if (h != null)
{
h(this, EventArgs.Empty);
29
- 37. }
}
}
このコードの実行結果を下記に示します。
add handler
add handler
1##OnNext(System.EventArgs)
2##OnNext(System.EventArgs)
1##OnNext(System.EventArgs)
2##OnNext(System.EventArgs)
dispose method call.
remove handler
remove handler
FromEvent メソッドのベントハンドラ登録処理とベントハンドラの登録解除処理にログを出
力するように仕込んだものが表示されています。 このことから、Dispose を呼ぶときちんとベン
トハンドラの登録解除が行われることがわかります。また、ベントが発行されたタミングで 2
つ登録した Observer の両方に対して通知がいっていることも確認できます。
3.6.2. Observable.Start メソッド
次に、簡単にバックグラウンドの処理を記述できる Start メソッドについて説明します。Start メ
ソッドは Action か Func<T>を引数に受け取り IObservable<Unit>か IObservable<T>を返し
ます。引数で受け取ったデリゲートの処理が終わると IObservable から結果が発行されます。コ
ード例を下記に示します。
// バックグラウンドで処理を開始
var source = Observable.Start(() =>
{
Console.WriteLine("background task start.");
Thread.Sleep(2000);
Console.WriteLine("background task end.");
return 1;
});
// 購読
Console.WriteLine("subscribe1");
var subscription1 = source.Subscribe(
i => Console.WriteLine("1##OnNext({0})", i),
ex => Console.WriteLine("1##OnError({0})", ex.Message),
() => Console.WriteLine("1##Completed()"));
30
- 38. // 処理が確実に終わるように 5 秒待つ
Console.WriteLine("sleep 5sec.");
Thread.Sleep(5000);
// Observable が発行する値の購読を停止
Console.WriteLine("dispose method call.");
subscription1.Dispose();
// 購読
Console.WriteLine("subscribe2");
var subscription2 = source.Subscribe(
i => Console.WriteLine("2##OnNext({0})", i),
ex => Console.WriteLine("2##OnError({0})", ex.Message),
() => Console.WriteLine("2##Completed()"));
subscription2.Dispose();
このサンプルで特徴的なのが、 Start メソッド内の処理が 2 秒で終わるにも関わらず 5 秒スリープ
した後に Subscribe をしている点です。通常の感覚では、既に処理が完了して値が発行された後
なので Subscribe しても何も起きないと考えられます。しかし、Start メソッドの戻り値の
IObservable<T>は最後の処理結果をキャッシュしています。 そのため、Subscribe されるとキャ
ッシュしている値と OnCompleted を発行します。
この例の実行結果では、最初の Subscribe と二回目の Subscribe それぞれで、OnNext と
OnCompleted の処理が呼ばれます。実行結果を下記に示します。
subscribe1
sleep 5sec. ← ここで 5 秒スリープしている
background task start.
background task end.
1##OnNext(1)
1##Completed()
dispose method call.
subscribe2 ← このタミングでは Start メソッドの処理は終了している
2##OnNext(1) ← OnNext と OnCompleted が通知される
2##Completed()
このことから、Start メソッドで作成する IObservable<T>は、Start メソッドが完了するまでは
Hot な Observable で処理が終了したあとは Cold な Observable になるという 2 面性をもつとい
う特徴があることが確認できます。
31
- 39. 3.6.3. Observable.ToAsync メソッド
次は、ToAsync メソッドを紹介します。このメソッドも Start メソッドと同様に重たい処理をバ
ックグラウンドでやるために使用できます。Start メソッドとの違いは ToAsync の戻り値にあら
われています。シグネチャを下記に示します。
public static Func<IObservable<T>> ToAsync<T>(Func<T> function)
引数に重たいことをやる処理を渡して、戻り値が IObservable<T>を返すデリゲートになってい
ます。この戻り値のデリゲートを呼び出すことで ToAsync の引数に渡した処理がはじめて実行さ
れます。Start は、Start メソッドを呼び出した直後から処理が開始されましたが、ToAsync を使
うと処理の開始のタミングを柔軟に制御できます。
ここで示したメソッドのシグネチャは数十個あるオーバーロードの 1 つになります。ToAsync に
はほかにも戻り値が無いケースや引数が大量にあるケースに備えて膨大な数のオーバーロードが
あります。完全なオーバーロードのリストについては下記の MSDN のリフゔレンスを参照してく
ださい。
Observable.ToAsync Method :
http://msdn.microsoft.com/en-us/library/system.reactive.linq.observable.toasync(v=VS.
103).aspx
コード例を下記に示します。
// 戻り値は Func<IObservable<T>>
var source = Observable.ToAsync(() =>
{
Console.WriteLine("background task start.");
Thread.Sleep(2000);
Console.WriteLine("background task end.");
return 1;
});
// ToAsync はデリゲートを返すので Invoke() or ()をしないと処理が開始されない
Console.WriteLine("source() call.");
var invokedSource = source.Invoke();
var subscription1 = invokedSource.Subscribe(
i => Console.WriteLine("1##OnNext({0})", i),
ex => Console.WriteLine("1##OnError({0})", ex.Message),
() => Console.WriteLine("1##Completed()"));
// 処理が確実に終わるように 5 秒待つ
Console.WriteLine("sleep 5sec.");
Thread.Sleep(5000);
32
- 40. // Observable が発行する値の購読を停止
Console.WriteLine("dispose method call.");
subscription1.Dispose();
// 購読
Console.WriteLine("subscribe2");
var subscription2 = invokedSource.Subscribe(
i => Console.WriteLine("2##OnNext({0})", i),
ex => Console.WriteLine("2##OnError({0})", ex.Message),
() => Console.WriteLine("2##Completed()"));
subscription2.Dispose();
ポントは、ToAsync の戻り値に対して Invoke をしているところです。ここで初めて ToAsync
に渡した処理の実行がはじまります。実行結果を下記に示します。
source() call.
background task start.
sleep 5sec.
background task end.
1##OnNext(1)
1##Completed()
dispose method call.
subscribe2
2##OnNext(1)
2##Completed()
ここでも Start メソッドの例と同じように ToAsync の処理が終わった後に Subscribe しているに
も関わらず OnNext と OnCompleted が呼ばれていることがわかります。非同期で処理を行う
IObservable<T>は全体的にこのような動きを行うので覚えておきましょう。
3.6.4. Observable.FromAsyncPattern メソッド
これで、一連のフゔクトリメソッドの紹介は最後になります。 最後を飾るのは FromAsyncPattern
メソッドです。このメソッドは名前が示す通り.NET Framework で使われている非同期呼び出し
のパターンから IObservable<T>を作成します。
// 重たい処理
Func<int, int, int> asyncProcess = (x, y) =>
{
Console.WriteLine("process start.");
Thread.Sleep(2000);
Console.WriteLine("process end.");
33
- 41. return x + y;
};
// 因みに非同期呼び出しは普通に書くとこんな感じ
// asyncProcess.BeginInvoke(
// 10, 2,
// ar =>
// {
// var ret = asyncProcess.EndInvoke(ar);
// // do something
// },
// null);
var asyncPattern = Observable.FromAsyncPattern<int, int, int>(
asyncProcess.BeginInvoke,
asyncProcess.EndInvoke);
var source = asyncPattern(10, 2);
// 処理中に購読開始
Console.WriteLine("subscribe2");
var subscription1 = source.Subscribe(
i => Console.WriteLine("1##OnNext({0})", i),
ex => Console.WriteLine("1##OnError({0})", ex.Message),
() => Console.WriteLine("1##Completed()"));
// 確実に処理が終わるように 5 秒待つ
Console.WriteLine("sleep 5sec");
Thread.Sleep(5000);
// Observable が発行する値の購読を停止
Console.WriteLine("dispose method call.");
subscription1.Dispose();
// 処理が完了したあとに購読
Console.WriteLine("subscribe2");
var subscription2 = source.Subscribe(
i => Console.WriteLine("2##OnNext({0})", i),
ex => Console.WriteLine("2##OnError({0})", ex.Message),
34
- 42. () => Console.WriteLine("2##Completed()"));
// 購読解除
Console.WriteLine("dispose method call.");
subscription2.Dispose();
普通はコールバックで書く非同期呼び出しを IObservable<T>にラッピングします。このメソッ
ドも ToAsync と同様に戻り値がデリゲートなので、デリゲートを呼び出すことで非同期処理が開
始されます。実行結果を下記に示します。
process start.
subscribe2
sleep 5sec
process end.
1##OnNext(12)
1##Completed()
dispose method call.
subscribe2
2##OnNext(12)
2##Completed()
dispose method call.
ここでも、処理が終わった後に Subscribe をしても OnNext と OnCompleted が呼ばれているこ
とがわかります。
4. IObservable の拡張メソッド
ここまで IObservable<T>を作成するための様々なフゔクトリメソッドを見てきました。ここで
は、視点を変えて IObservable<T>を作成したあとに使用できる IObservable<T>に定義された
拡張メソッドを紹介します。
4.1. LINQ のメソッド
IObservable<T>の拡張メソッドも、 ほとんどが System.Reactive.Linq.Observable クラスに定
義されています。その中でも LINQ でお馴染みの Where や Select メソッドも含まれています。
LINQ のメソッドは IObservable<T>が発行した値に対して Where でフゖルタリングしたり
Select で変換したりできます。下図は、そのメージを表しています。
35
- 43. 下図は Where でフゖルタリングされた場合を表しています。Where でフゖルタリングされた場
合は Select や Subscribe まで処理はいきません。
実際にコードで動きを確認してみます。
// 値を発行するための Subject
var subject = new Subject<int>();
// AsObservable で IObservable<T>に変換(ゕップキャストで Subject<T>に戻せない
var source = subject.AsObservable();
// 普通に Subscribe
source.Subscribe(
value => Console.WriteLine("1##OnNext({0})", value),
ex => Console.WriteLine(ex.Message),
() => Console.WriteLine("1##OnCompleted()"));
// 奇数のみ通すようにフゖルタリングして
source.Where(i => i % 2 == 1)
// 文字列に加工して
.Select(i => i + "は奇数です")
// 表示する
36
- 44. .Subscribe(
value => Console.WriteLine("2##OnNext({0})", value),
ex => Console.WriteLine(ex.Message),
() => Console.WriteLine("2##OnCompleted()"));
// 1~10 の値を subject に対して発行する
Observable.Range(1, 10).ForEach(i => subject.OnNext(i));
// 完了通知を行う
subject.OnCompleted();
上記のコードでは、1~10 の値を Subject<T>を使って発行しています。Subject<T>は、
AsObservable メソッドで IObservable<T>に変換できます。AsObservable をしなくても
Subject<T>クラスは IObservable<T>を継承しているので差支えはないのですが、純粋な
IObservable<T>に、なんとなくしたかったのでこの例では変換しています。通常は、内部に
Subject<T>クラスを抱えたクラスが外部に IObservable<T>を公開するときに、ダウンキャス
トされても Subject<T>型に戻せない IObservable<T>を返すために使用します。
その他に、今回初登場のメソッドとして ForEach メソッドがあります。これは引数に渡された
Action<T>を、IObservable<T>から発行された値を引数に渡して使用します。平たく言うと for
ループです。ここでは 1~10 の値を Observable.Range で作成して ForEach で Subject<T>に
流し込んでいます。
今回の本題である拡張メソッドは Where メソッドと Select メソッドになります。Where メソッ
ドは引数で渡した Func<T, bool>が true を返す要素のみを通します。Select メソッドは引数で
渡した Func<T, U>で値を変換します。上記の例では奇数以外の値をフゖルタリングして「X は
奇数です」という文字列に変換して、Subscribe 内で標準出力に出力しています。動作の違いを見
るために、Where や Select を使用しないで Subscribe もしています。
このプログラムの実行結果を下記に示します。
1##OnNext(1)
2##OnNext(1 は奇数です)
1##OnNext(2)
1##OnNext(3)
2##OnNext(3 は奇数です)
1##OnNext(4)
1##OnNext(5)
2##OnNext(5 は奇数です)
1##OnNext(6)
1##OnNext(7)
2##OnNext(7 は奇数です)
1##OnNext(8)
1##OnNext(9)
37
- 45. 2##OnNext(9 は奇数です)
1##OnNext(10)
1##OnCompleted()
2##OnCompleted()
実行結果から、Where によるフゖルタリングが行われていることと、Select による変換が行われ
ていることがわかると思います。
4.2. 単一の値を取得するメソッド
ここでは、IObservable<T>のシーケンスから単一の値を取得するために利用するメソッドにつ
いて説明します。
4.2.1. First メソッドと Last メソッド
まず、最初の値を取得する First メソッドと、最後の値を取得する Last メソッドについて説明し
ます。各メソッドのシグネチャは以下のようになります。
// First メソッド
public static TSource First<T>(
this IObservable<T> source
)
// Last メソッド
public static TSource Last<T>(
this IObservable<T> source
)
どちらのメソッドも IObservable<T>から T の値を取得します。First メソッドのコード例を下記
に示します。
// Observable を作成前のタムスタンプを表示
Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);
var firstResult = Observable
// 5 秒間隔で値を発行する
.Interval(TimeSpan.FromSeconds(5))
.Select(i => "value is " + i)
// 最初の値を取得
.First();
// First の実行が終わった後のタムスタンプを表示
Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);
// 取得した値を表示
Console.WriteLine("firstResult: {0}", firstResult);
38