Reactive extensions入門v0.1

211
Reactive Extensions 入門 2012/03/05 0.1 版 okazuk
  • Upload

    -
  • Category

    Documents

  • view

    10.093
  • download

    1

Transcript of Reactive extensions入門v0.1

Page 1: Reactive extensions入門v0.1

Reactive Extensions入門

2012/03/05

0.1版

okazuk

Page 2: Reactive extensions入門v0.1

ii

改版履歴

版数 内容 日付

0.1 基本メソッドの説明を書いた初版作成 2012/03/05

Page 3: Reactive extensions入門v0.1

i

内容

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

Page 4: Reactive extensions入門v0.1

ii

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

Page 5: Reactive extensions入門v0.1

iii

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

Page 6: Reactive extensions入門v0.1

iv

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

Page 7: Reactive extensions入門v0.1

v

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

Page 8: Reactive extensions入門v0.1

1

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さんには、サトの情報や私の理解不足による誤った

記載など丁寧に対応していただきとても感謝しています。

Page 9: Reactive extensions入門v0.1

2

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>を生成するメソッドや、非同期呼び出し、

Page 10: Reactive extensions入門v0.1

3

タマー、シーケンス、特定のルールで生成される値の集合 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);

}

Page 11: Reactive extensions入門v0.1

4

// 完了通知が来たときの処理

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"));

Page 12: Reactive extensions入門v0.1

5

}

// エラーが起きたので処理は終了

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;

Page 13: Reactive extensions入門v0.1

6

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

Page 14: Reactive extensions入門v0.1

7

{

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

Page 15: Reactive extensions入門v0.1

8

## 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つ監視役を登録

Page 16: Reactive extensions入門v0.1

9

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();

Page 17: Reactive extensions入門v0.1

10

引数を 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()

Page 18: Reactive extensions入門v0.1

11

{

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クラスのフゔクトリメソッドから基本的なものをいくつか紹介

します。

Page 19: Reactive extensions入門v0.1

12

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();

Page 20: Reactive extensions入門v0.1

13

これを実行すると下記のようになります。

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()

Page 21: Reactive extensions入門v0.1

14

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ずつンクリメントして, 値を二乗したものを発行する

Page 22: Reactive extensions入門v0.1

15

// 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);

Page 23: Reactive extensions入門v0.1

16

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()

Page 24: Reactive extensions入門v0.1

17

# 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();

Page 25: Reactive extensions入門v0.1

18

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)

Page 26: Reactive extensions入門v0.1

19

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>を取得する際にリソースの確保を行うケースに使用できるフゔクト

リメソッドについて説明します。

Page 27: Reactive extensions入門v0.1

20

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());

// 購読

Page 28: Reactive extensions入門v0.1

21

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();

Page 29: Reactive extensions入門v0.1

22

// 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の実行メージを下図に示します。

Page 30: Reactive extensions入門v0.1

23

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が実行されたあとに数秒間待機し

て購読が停止していることを確認しています。

Page 31: Reactive extensions入門v0.1

24

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)

Page 32: Reactive extensions入門v0.1

25

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),

Page 33: Reactive extensions入門v0.1

26

() => 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>を作成し、1回 Subscribeしたあと

に 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>(…)というメ

ソッドがあります。このメソッドのシグネチャを下記に示します。

Page 34: Reactive extensions入門v0.1

27

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()"));

Page 35: Reactive extensions入門v0.1

28

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 だけでいい

Page 36: Reactive extensions入門v0.1

29

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);

Page 37: Reactive extensions入門v0.1

30

}

}

}

このコードの実行結果を下記に示します。

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()"));

Page 38: Reactive extensions入門v0.1

31

// 処理が確実に終わるように 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面性をもつとい

う特徴があることが確認できます。

Page 39: Reactive extensions入門v0.1

32

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);

Page 40: Reactive extensions入門v0.1

33

// 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.");

Page 41: Reactive extensions入門v0.1

34

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),

Page 42: Reactive extensions入門v0.1

35

() => 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で変換したりできます。下図は、そのメージを表しています。

Page 43: Reactive extensions入門v0.1

36

下図は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 + "は奇数です")

// 表示する

Page 44: Reactive extensions入門v0.1

37

.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)

Page 45: Reactive extensions入門v0.1

38

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);

Page 46: Reactive extensions入門v0.1

39

5秒間隔で値を発行する IObservable<long>から最初の値を取得しています。(正確には Select

メソッドで発行された値を加工していますが)そして、Firstメソッドが呼び出される前と後にタ

ムスタンプを表示するコードを入れています。このコードの実行結果を下記に示します。

Timestamp 2012/01/03 18:25:45.783

Timestamp 2012/01/03 18:25:50.829

firstResult: value is 0

三行目に、Firstメソッドで取得した値が出力されていることが確認できます。このように、First

メソッドでは、IObservable<T>から最初に発行された値を取得できます。ここで注目したいの

は、Firstメソッドの呼び出しの前後に入れているタムスタンプを表示するメソッドの表示内容

です。18:25:45から 18:25:50となっていることからわかるように Firstメソッドの呼び出し前

と呼び出し後で 5秒たっていることがわかります。

Firstメソッドの特徴として、最初の値が IObservable<T>のシーケンスから発行されるまで、実

行しているスレッドをブロックするという特徴があります。そのため非同期処理などの結果を待つ

のに使用することも可能です。(基本はブロックしないように作るのが一番いいです)また、下記

のコードのように、永久に値が発行されない IObservable<T>に対して Firstメソッドを呼び出す

と終了しないプログラムになるので注意してください。

var s = new Subject<int>();

s.First(); // 最初の値が発行されるまで処理が止まる

s.OnNext(10); // ここには永久に到達しない

Lastメソッドも、Firstメソッドと同じような動きをします。Lastメソッドの場合はOnCompleted

が呼ばれない限り値を返さないので、永遠に終わらないタマーやベントに対して呼び出すと、

そこでプログラムがフリーズしてしまうので注意してください。Lastメソッドのコード例を下記

に示します。

// Observableを作成前のタムスタンプを表示

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

var lastResult = Observable

// 1秒間隔で値を 5つ発行する IObservable

.Generate(0, i => i < 5, i => ++i, i => "value is " + i, i => TimeSpan.FromSeconds(1))

// 最後の値を取得

.Last();

// Lastの実行が終わった後のタムスタンプを表示

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

// Lastの実行結果を取得

Console.WriteLine("lastResult: {0}", lastResult);

このコードでは value is 0~value is 4までの 5つの値を 1秒間隔で実行する

IObservable<string>のシーケンスの最後の値を取得しています。Firstメソッドと同様に Last

メソッドの呼び出し前と呼び出し後にタムスタンプを表示しています。実行結果を下記に示しま

す。

Page 47: Reactive extensions入門v0.1

40

Timestamp 2012/01/03 18:38:58.143

Timestamp 2012/01/03 18:39:03.257

lastResult: value is 4

Lastメソッドもタムスタンプの結果を確認するとわかるとおり、実行しているスレッドをブロ

ックして結果を待機します。今回の例では、Generateメソッドが発行する最後の値の value is 4

を待っているため 5秒間スレッドをブロックしています。

4.2.1.1. 値が存在しない場合の Firstメソッドと Lastメソッドの挙動

Firstメソッドと Lastメソッドですが、値が存在する場合は、その値を返しますが、値が存在しな

い場合には例外を発生させます。Firstメソッドの場合のコード例を下記に示します。

// 1つも要素の無い IObservable

var noElementsSequence = new Subject<string>();

noElementsSequence.OnCompleted();

try

{

// 要素が無いものに対して最初の要素を要求

var firstResult = noElementsSequence.First();

}

catch (InvalidOperationException e)

{

// 何もないので例外が発生する

Console.WriteLine("Exception: {0}", e.Message);

}

コードの実行結果を下記に示します。

Exception: Sequence contains no elements.

このように、取得する要素が無い(空の IObservable<T>のシーケンス)場合は

InvalidOperationExceptionを発行します。Lastメソッドについても同様です。以下にコード例

と実行結果を示します。

// 1つも要素の無い IObservable

var noElementsSequence = new Subject<string>();

noElementsSequence.OnCompleted();

try

{

// 最後の要素の取得

var lastResult = noElementsSequence.Last();

Page 48: Reactive extensions入門v0.1

41

}

catch (InvalidOperationException e)

{

// 何もないので例外が発生する

Console.WriteLine("Exception: {0}", e.Message);

}

Exception: Sequence contains no elements.

4.2.1.2. Firstメソッドと Lastメソッドのオーバーロード

Firstメソッドと Lastメソッドには、Func<T, bool>型のデリゲートを受け取るオーバーロード

があります。これは、デリゲートが trueを返す最初または最後の要素を返すという点以外は、通

常の Firstメソッドと Lastメソッドと共通の動きをします。コードと実行結果については、割愛

します。

4.2.2. FirstOrDefaultメソッドと LastOrDefault メソッド

ここでは、FirstOrDefaultメソッドと LastOrDefaultメソッドについて説明します。このメソッ

ドも Firstメソッドや Lastメソッドと同様に IObservable<T>のシーケンスから最初の値や最後

の値を取得するために使用します。また、値が取得できるまでの間はスレッドをブロックする点も

共通の動作になります。挙動が異なるのは、要素が存在しない IObservable<T>のシーケンスに

対して呼び出した場合になります。FirstメソッドとLastメソッドが例外を発生させるのに対して、

この FirstOrDefaultメソッドと LastOrDefaultメソッドはデフォルト値を返します。コード例を

下記に示します。

まずは、値が取得できるケースの FirstOrDefaultメソッドのコードです。

// 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)

// 最初の値を取得

.FirstOrDefault();

// Firstの実行が終わった後のタムスタンプを表示

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

// 取得した値を表示

Console.WriteLine("firstResult: {0}", firstResult);

Firstメソッドのコード例と異なる点はメソッドの呼び出しを FirstOrDefaultに変更した点だけで

す。実行結果を下記に示します。

Timestamp 2012/01/03 18:55:08.572

Page 49: Reactive extensions入門v0.1

42

Timestamp 2012/01/03 18:55:13.623

firstResult: value is 0

実行結果も Firstメソッドと変わりません。次に、空の IObservable<T>のシーケンスに対して

FirstOrDefaultメソッドを呼び出した場合のコード例を下記に示します。

// 1つも要素の無い IObservable

var noElementsSequence = new Subject<string>();

noElementsSequence.OnCompleted();

// 最初の値 or デフォルト値を取得

var firstResult = noElementsSequence.FirstOrDefault();

// 結果を出力。この場合は nullが表示される。

Console.WriteLine("firstResult: {0}", firstResult ?? "null");

このコードでは、FirstOrDefaultメソッドの戻り値は string型のデフォルト値である nullになり

ます。そのため、実行結果にも nullが表示されます。実行結果を下記に示します。

firstResult: null

LastOrDefaultメソッドについても同様の動きとなるため、コード例と実行結果は割愛します。

4.2.2.1. FirstOrDefaultメソッドと LastOrDefaultメソッドのオーバーロード

FirstOrDefaultメソッドと LastOrDefaultメソッドにも Firstメソッドと Lastメソッドと同様に

Func<T, bool>型のデリゲートを受け取るオーバーロードがあります。こちらも、デリゲートが

trueを返す要素のみに絞る点以外は、挙動は同じためコード例と実行結果は割愛します。

4.2.3. ElementAtメソッド

ここでは、ElementAtメソッドについて説明します。ElementAtメソッドはンデックスで指定

した要素を取得するメソッドになります。Firstメソッドや Lastメソッドが単一の値を返していた

のに対して ElementAtメソッドの戻り値は IObservable<T>になります。ElementAtメソッド

のシグネチャを下記に示します。

public static IObservable<T> ElementAt<T>(

this IObservable<T> source,

int index

)

このようなシグネチャになっているため ElementAtの結果の値を処理する場合は、別途 Firstメ

ソッドを呼ぶか Subscribeして OnNextで値を処理する必要があります。ElementAtメソッドの

コード例を下記に示します。

// 待機用のWaitHandle

var gate = new EventWaitHandle(false, EventResetMode.AutoReset);

// ElementAt前のタムスタンプを表示

Page 50: Reactive extensions入門v0.1

43

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

Observable

// 1秒間隔で 5回値を発行する

.Generate(0, i => i < 5, i => ++i, i => "value is " + i, i => TimeSpan.FromSeconds(1))

// 3番目の要素を取得する

.ElementAt(3)

// この一連のシーケンスの最後で待機しているスレッドを解放する

.Finally(() => gate.Set())

// 購読

.Subscribe(

// 値を表示する(nullの場合は nullと表示する)

i => Console.WriteLine("elementAt3: {0}", i ?? "null"),

// 例外が発生した場合は例外のメッセージを表示する

ex => Console.WriteLine("Exception: {0}, {1}", ex.GetType().Name, ex.Message),

// 完了を示すメッセージを表示する

() => Console.WriteLine("OnCompleted"));

// 一連のメソッドチェンが終わった時のタムスタンプを表示する

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

// gate.Set()が呼ばれるまで停止

gate.WaitOne();

// gate.Setが呼ばれた後のタムスタンプを表示する

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

Generateメソッドを使って value is 0~value is 4までの 5つの値を発行する

IObservable<string>のシーケンスを作成して、ElementAt(3)で 3番目の要素を取得しています。

そして、ElementAtの結果の IObservable<string>に対して Subscribeメソッドを呼び出して、

結果を表示しています。

コードの本題とは関係ありませんが、EventWaitHandle クラスを使って Generate メソッドで作

成した IObservable<string>のシーケンスが終了するのを確実に待ち合わせています。これは、

Generateメソッドや ElementAtメソッド, Subscribeメソッドの処理がバックグラウンドのスレ

ッドで実行されているため、待ち合わせをしないと、Subscribe メソッドの処理が走る前にプログ

ラムが終了してしまうためです。EventWaitHandle クラスについての説明は MSDN を参照してく

ださい。http://msdn.microsoft.com/ja-jp/library/system.threading.eventwaithandle.aspx

コードの実行結果を下記に示します。

Timestamp 2012/01/03 19:13:33.498

Timestamp 2012/01/03 19:13:33.538

elementAt3: value is 3

OnCompleted

Page 51: Reactive extensions入門v0.1

44

Timestamp 2012/01/03 19:13:37.588

実行結果の 3行目で value is 3と 3番目の値が表示されていることが確認できます。また、最初

の 2つのタムスタンプの表示は、ElementAtを含む一連のメソッドチェンの前後で出力して

いますが、ほとんど時間が経過していないことがわかります。そして、最後のタムスタンプの出

力までに 4秒の時間が経過していることが確認できます。1つの値が 1秒間隔で発行されている

ため 3番目(0オリジンなので正確には 4番目)の値が発行されるまでの時間と合致しています。

4.2.3.1. 値が存在しない場合の ElementAtメソッドの挙動

ElementAtで指定したンデックスに値が存在しない場合の挙動について説明します。この場合

も Firstメソッドや Lastメソッドと同様に例外が発生しますが、IObservable<T>のシーケンス

内で起きる例外なので Subscribeの OnErrorや Catchメソッドで捕捉することが可能です。コー

ド例を下記に示します。

// 待機用のWaitHandle

var gate = new EventWaitHandle(false, EventResetMode.AutoReset);

// ElementAt前のタムスタンプを表示

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

Observable

// 3要素しか発行しない

.Generate(0, i => i < 2, i => ++i, i => "value is " + i, i => TimeSpan.FromSeconds(1))

// 3番目の要素を取得する

.ElementAt(3)

// この一連のシーケンスの最後で待機しているスレッドを解放する

.Finally(() => gate.Set())

// 購読

.Subscribe(

// 値を表示する(nullの場合は nullと表示する)

i => Console.WriteLine("elementAt3: {0}", i ?? "null"),

// 例外が発生した場合は例外のメッセージを表示する

ex => Console.WriteLine("Exception: {0}, {1}", ex.GetType().Name, ex.Message),

// 完了を示すメッセージを表示する

() => Console.WriteLine("OnCompleted"));

// 一連のメソッドチェンが終わった時のタムスタンプを表示する

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

// gate.Set()が呼ばれるまで停止

gate.WaitOne();

// gate.Setが呼ばれた後のタムスタンプを表示する

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

実行結果を下記に示します。

Page 52: Reactive extensions入門v0.1

45

Timestamp 2012/01/03 19:20:57.826

Timestamp 2012/01/03 19:20:57.866

Exception: ArgumentOutOfRangeException, 指定された引数は、有効な値の範囲内にありません。

パラメーター名: index

Timestamp 2012/01/03 19:21:00.053

実行結果からわかるように、ArgumentOutOfRangeExceptionが発生します。

4.2.4. ElementAtOrDefaultメソッド

ここでは、ElementAtOrDefaultメソッドについて説明します。ElementAtOrDefaultメソッドは

通常時の動作は ElementAtメソッドと同じになります。挙動が異なるのは、引数に範囲外の値を

渡すと ElementAtメソッドでは例外が発生していたのに対して、ElementAtOrDefaultメソッド

ではデフォルト値を発行するという点です。

ンデックスに範囲外の値を渡したときの ElementAtOrDefaultメソッドのコード例について下

記に示します。

// 待機用のWaitHandle

var gate = new EventWaitHandle(false, EventResetMode.AutoReset);

// ElementAt前のタムスタンプを表示

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

Observable

// 3要素しか発行しない

.Generate(0, i => i < 2, i => ++i, i => "value is " + i, i => TimeSpan.FromSeconds(1))

// 3番目の要素を取得する

.ElementAtOrDefault(3)

// この一連のシーケンスの最後で待機しているスレッドを解放する

.Finally(() => gate.Set())

// 購読

.Subscribe(

// 値を表示する(nullの場合は nullと表示する)

i => Console.WriteLine("elementAt3: {0}", i ?? "null"),

// 例外が発生した場合は例外のメッセージを表示する

ex => Console.WriteLine("Exception: {0}, {1}", ex.GetType().Name, ex.Message),

// 完了を示すメッセージを表示する

() => Console.WriteLine("OnCompleted"));

// 一連のメソッドチェンが終わった時のタムスタンプを表示する

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

// gate.Set()が呼ばれるまで停止

gate.WaitOne();

Page 53: Reactive extensions入門v0.1

46

// gate.Setが呼ばれた後のタムスタンプを表示する

Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

0~2の 3つの要素しか発行していない IObservable<string>のシーケンスに対して

ElementAtOrDefault(3)のように範囲外の値を要求しています。実行結果を下記に示します。

Timestamp 2012/01/03 19:27:17.89

Timestamp 2012/01/03 19:27:17.929

elementAt3: null

OnCompleted

Timestamp 2012/01/03 19:27:19.965

三行目に elementAt3: nullと表示されていることから、範囲外の値を要求しても例外が発生せず

にデフォルト値が発行されていることが確認できます。

4.2.5. Single メソッド

ここでは、Singleメソッドについて説明します。Singleメソッドは、名前のとおり単一の値を返

すメソッドになります。メソッドのシグネチャを下記に示します。

public static TSource Single<TSource>(this IObservable<TSource> source)

このメソッドの特徴は、単一の要素が返ることが確定するまで結果を返さないことです。コード例

を下記に示します。

// 実行開始時間を出力

Console.WriteLine("Start {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

var singleResult = Observable

// 1秒後に 1つだけ値を発行する

.Generate(1, i => i == 1, i => ++i, i => i, _ => TimeSpan.FromSeconds(1))

// 発行された値をダンプ

.Do(i => Console.WriteLine("Dump {0:yyyy/MM/dd HH:mm:ss.FFF}, Value = {1}", DateTime.Now,

i))

// 単一の値を取得する

.Single();

// 結果の出力

Console.WriteLine("singleResult: {0}", singleResult);

// 終了時の時間を出力

Console.WriteLine("End {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

Generateメソッドを使い、1秒後に 1つだけ値を発行しています。そして、Doメソッドで発行

された値とタムスタンプを表示して Singleメソッドにつなげています。実行結果を下記に示し

ます。

Start 2012/01/09 23:14:19.68

Dump 2012/01/09 23:14:20.906, Value = 1

Page 54: Reactive extensions入門v0.1

47

singleResult: 1

End 2012/01/09 23:14:20.91

3行目で Singleメソッドの戻り値が表示されています。注目する点は、Startと Endの間で 1秒

の差があることです。このことから、Singleメソッドは値を返すまで実行中のスレッドをブロッ

クすることが確認できます。

もう1つの Singleメソッドの特徴として、単一の要素が返らない(2個以上や 0個の場合)こと

が確定した時点で InvalidOperationExceptionの例外をスローします。この動作を示すコード例

を下記に示します。

// 実行開始時間を出力

Console.WriteLine("Start {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

try

{

var singleResult = Observable

// 1秒間隔で 2つの値を出力

.Generate(0, i => i < 2, i => ++i, i => i, i => TimeSpan.FromSeconds(1))

// 発行された値を出力

.Do(i => Console.WriteLine("Dump {0:yyyy/MM/dd HH:mm:ss.FFF}, Value = {1}",

DateTime.Now, i))

// 単一の値を取得する

.Single();

// 結果を出力

Console.WriteLine("singleResult: {0}", singleResult);

}

catch (InvalidOperationException ex)

{

// 単一の値を取得しようとしたら 2つ以上値が流れてきたので例外になる

Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);

}

// 終了時の時間を出力

Console.WriteLine("End {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

実行結果を下記に示します。

Start 2012/01/09 23:21:24.123

Dump 2012/01/09 23:21:25.133, Value = 0

Dump 2012/01/09 23:21:26.147, Value = 1

InvalidOperationException: Sequence contains more than one element.

End 2012/01/09 23:21:26.147

2つ目の Dump後に InvalidOperationExceptionがスローされていることが確認できます。

Page 55: Reactive extensions入門v0.1

48

4.2.5.1. Singleメソッドのオーバーロード

Singleメソッドには Firstメソッドや Lastメソッドや ElementAtメソッドと同様に、値のフゖル

タリングを行うための Func<T, bool>型のデリゲートを渡すオーバーロードがあります。これを

つかうことで、複数の値が発行される IObservable<T>のシーケンスから特定の条件に合致する

単一の要素を取り出すことが出来ます。メソッドのシグネチャを下記に示します。

public static TSource Single<TSource>(this IObservable<TSource> source, Func<TSource, bool>

predicate)

このオーバーロードを使ったコード例を下記に示します。

// 実行開始時間を出力

Console.WriteLine("Start {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

var singleResult = Observable

// 1秒間隔で 0~4の値を発行する

.Generate(

0, i => i < 5, i => ++i, i => i, i => TimeSpan.FromSeconds(1))

// 発行された値を出力

.Do(i => Console.WriteLine("Dump {0:yyyy/MM/dd HH:mm:ss.FFF}, Value = {1}", DateTime.Now,

i))

// 値が 3のものを 1つだけ取得したい

.Single(i => i == 3);

// 結果を出力

Console.WriteLine("singleResult: {0}", singleResult);

// 終了時の時間を出力

Console.WriteLine("End {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

Generateメソッドを使って 0~4の値を 1秒間隔で発行しています。そして、Singleメソッドの

引数で値が 3のものを取得するように指定しています。このコードの実行結果を下記に示します。

Start 2012/01/09 23:40:46.204

Dump 2012/01/09 23:40:47.215, Value = 0

Dump 2012/01/09 23:40:48.229, Value = 1

Dump 2012/01/09 23:40:49.243, Value = 2

Dump 2012/01/09 23:40:50.257, Value = 3

Dump 2012/01/09 23:40:51.271, Value = 4

singleResult: 3

End 2012/01/09 23:40:51.272

0~4の 5つの値が発行されていますが、Singleメソッドでは例外がスローされずに引数で指定し

た 3が取得できています。

Page 56: Reactive extensions入門v0.1

49

このオーバーロードを使った場合も、引数で指定した条件にあうものが複数ある場合や、1つも条

件にあわない場合は InvalidOperationExceptionの例外がスローされます。コード例を下記に示

します。

// 実行開始時間を出力

Console.WriteLine("Start {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

try

{

var singleResult = Observable

// 1秒間隔で 0~4の値を発行する

.Generate(

0, i => i < 5, i => ++i, i => i, i => TimeSpan.FromSeconds(1))

// 発行された値を出力

.Do(i => Console.WriteLine("Dump {0:yyyy/MM/dd HH:mm:ss.FFF}, Value = {1}",

DateTime.Now, i))

// 値が 10より大きいものを 1つだけ取得したい

.Single(i => i > 10);

// 結果を出力

Console.WriteLine("singleResult: {0}", singleResult);

}

catch (InvalidOperationException ex)

{

// 単一の値を取得しようとしたら 1つも値が無かったのでエラー

Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);

}

// 終了時の時間を出力

Console.WriteLine("End {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

0~4の値が発行される IObservable<int>のシーケンスに対して 10以上の値を 1つ取得するよ

うに Singleメソッドを呼び出しています。そのため InvalidOperationExceptionの例外が発生し

ます。実行結果を下記に示します。

Start 2012/01/09 23:46:39.903

Dump 2012/01/09 23:46:40.917, Value = 0

Dump 2012/01/09 23:46:41.971, Value = 1

Dump 2012/01/09 23:46:42.976, Value = 2

Dump 2012/01/09 23:46:43.989, Value = 3

Dump 2012/01/09 23:46:45.013, Value = 4

InvalidOperationException: Sequence contains no elements.

End 2012/01/09 23:46:45.014

Page 57: Reactive extensions入門v0.1

50

4.2.6. SingleOrDefaultメソッド

ここでは、SingleOrDefaultメソッドについて説明します。このメソッドは Singleメソッドと同

様に IObservable<T>のシーケンスから単一の値を取得するために使用します。Singleメソッド

と異なる点は、1つも値が取得できなかった場合に、デフォルト値を返すところです。まずは、正

常系の動作のコードを下記に示します。

// 実行開始時間を出力

Console.WriteLine("Start {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

var singleResult = Observable

// 1秒間隔で 0~4の値を発行する

.Generate(

0, i => i < 5, i => ++i, i => i, i => TimeSpan.FromSeconds(1))

// デフォルト値を nullにしたいので string型に変換

.Select(i => i.ToString())

// 発行された値を出力

.Do(i => Console.WriteLine("Dump {0:yyyy/MM/dd HH:mm:ss.FFF}, Value = {1}", DateTime.Now,

i))

// 値が”3"のものを 1つだけ取得したい

.SingleOrDefault(i => i == "3");

// 結果を出力

Console.WriteLine("singleResult: {0}", singleResult ?? "null");

// 終了時の時間を出力

Console.WriteLine("End {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

実行結果を下記に示します。

Start 2012/01/09 23:54:25.959

Dump 2012/01/09 23:54:26.971, Value = 0

Dump 2012/01/09 23:54:27.984, Value = 1

Dump 2012/01/09 23:54:28.998, Value = 2

Dump 2012/01/09 23:54:30.014, Value = 3

Dump 2012/01/09 23:54:31.028, Value = 4

singleResult: 3

End 2012/01/09 23:54:31.029

次に、1つも値が取得できないケースのコード例を下記に示します。

// 空の IObservableシーケンス

var s = new Subject<string>();

s.OnCompleted();

Page 58: Reactive extensions入門v0.1

51

// 1つも値が取得できない場合は nullが返る

var singleResult = s.SingleOrDefault();

// 結果を出力

Console.WriteLine("singleResult: {0}", singleResult ?? "null");

このコードの実行結果を下記に示します。

singleResult: null

Singleメソッドでは InvalidOperationExceptionが発生していたケースですが、SingleOrDefault

を使うとデフォルト値(今回の例では string型なので null)を返すことが確認できます。

注意点は、SingleOrDefaultメソッドを使用した場合でも複数の値が取得できるケースではデフォ

ルト値ではなく InvalidOperationExceptionが発生するところです。この挙動が確認できるコー

ド例を下記に示します。

// 実行開始時間を出力

Console.WriteLine("Start {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

try

{

var singleResult = Observable

// 1秒間隔で 0~4の値を発行する

.Generate(

0, i => i < 5, i => ++i, i => i, i => TimeSpan.FromSeconds(1))

// デフォルト値を nullにしたいので string型に変換

.Select(i => i.ToString())

// 発行された値を出力

.Do(i => Console.WriteLine("Dump {0:yyyy/MM/dd HH:mm:ss.FFF}, Value = {1}",

DateTime.Now, i))

// 値を 1つだけ取得したい

.SingleOrDefault();

// 結果を出力

Console.WriteLine("singleResult: {0}", singleResult ?? "null");

}

catch (InvalidOperationException ex)

{

// SingleOrDefaultメソッドを使っても複数の値が取得できてしまうケースでは例外になる

Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);

}

// 終了時の時間を出力

Console.WriteLine("End {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);

Page 59: Reactive extensions入門v0.1

52

0~4の値を 1秒間隔で発生させて SingleOrDefaultメソッドを呼び出しています。実行結果を下

記に示します。

Start 2012/01/10 00:02:36.047

Dump 2012/01/10 00:02:37.063, Value = 0

Dump 2012/01/10 00:02:38.077, Value = 1

InvalidOperationException: Sequence contains more than one element.

End 2012/01/10 00:02:38.077

2つ目の値が発行されたタミングで例外が発生していることが確認できます。

4.2.6.1. SingleOrDefaultメソッドのオーバーロード

SingleOrDefaultメソッドにも Singleメソッドと同様に Func<T, bool>型のデリゲートを受け取

るオーバーロードがあります。このメソッドの挙動は、Singleメソッドの時と同様に引数で渡し

たデリゲートでフゖルタリングを行いつつ、SingleOrDefaultのコード例で示したように 1つも値

が取得できなかった場合にデフォルト値を返します。

動作は「4.2.6 SingleOrDefaultメソッド」と、「4.2.5.1 Singleメソッドのオーバーロード」で

示したものと、ほぼ同じような形になるため、コード例については割愛します。

4.3. 値と飛ばす、値を拾うメソッド

ここでは、指定した数や条件に応じて IObservable<T>のシーケンスから値を飛ばしたり、拾っ

たりするメソッドについて説明します。

4.3.1. Skipと Take メソッド

LINQの基本のメソッドのWhereと Selectの動作を確認したので、次は Skipと Takeを使用し

たいと思います。Skipは、指定した数だけ値を読み飛ばして、Takeは指定した数だけ値を通過さ

せるメージです。コード例を下記に示します。

// 値の発行元になる Subject

var s = new Subject<int>();

// 最初の 3つをスキップして、その後 3つを通知する

s.Skip(3).Take(3).Subscribe(

i => Console.WriteLine("OnNext({0})", i),

ex => Console.WriteLine("OnError({0})", ex.Message),

() => Console.WriteLine("OnCompleted()"));

// 1-10の値を流し込む

Observable.Range(1, 10).ForEach(i => s.OnNext(i));

Page 60: Reactive extensions入門v0.1

53

Skip(3)をして Take(3)をしているところに 1~10の値を発行しているので 4, 5, 6が Subscribe

しているところに通知されます。実行結果を下記に示します。

OnNext(4)

OnNext(5)

OnNext(6)

OnCompleted()

注目するのは、6が発行されたあとに OnCompletedが呼ばれている点です。Take(3)で値が 3つ

通過した後には、もう値が発行されないので、完了通知が発行されます。

4.3.2. Repeatメソッドとの組み合わせ

Skipと Takeの動作自体は非常にシンプルなので、これに Repeatを組み合わせてみようと思いま

す。Repeat自体は 3.2.3.1で紹介しているとおり、指定した回数繰り返しを行うメソッドです。

Skipと Takeと組み合わせることで下記のような動作をさせることが出来ます。コードを下記に

示します。

// 値の発行元になる Subject

var s = new Subject<int>();

// 最初の 3つをスキップして、その後 3つを通知することを繰り返す

s.Skip(3).Take(3).Repeat().Subscribe(

i => Console.WriteLine("OnNext({0})", i),

ex => Console.WriteLine("OnError({0})", ex.Message),

() => Console.WriteLine("OnCompleted()"));

// 1-20の値を流し込む

Observable.Range(1, 20).ForEach(i => s.OnNext(i));

先ほどのコードの Takeの後に Repeatを追加しています。引数の無い Repeatメソッドは、無限

に繰り返しを行います。どのような動きになるのか実行結果を下記に示します。

OnNext(4) <- 最初の Skip(3).Take(3)

OnNext(5) <- 最初の Skip(3).Take(3)

OnNext(6) <- 最初の Skip(3).Take(3)

OnNext(10) <- 2回目の Skip(3).Take(3)

OnNext(11) <- 2回目の Skip(3).Take(3)

OnNext(12) <- 2回目の Skip(3).Take(3)

OnNext(16) <- 3回目の Skip(3).Take(3)

OnNext(17) <- 3回目の Skip(3).Take(3)

OnNext(18) <- 3回目の Skip(3).Take(3)

Page 61: Reactive extensions入門v0.1

54

Repeatを追加することで 3つ値をスキップして 3つ値を発行するという動作を繰り返しています。

このように IObservable<T>の拡張メソッドを組み合わせることで様々な IObservable<T>を定

義することが出来ます。

4.3.3. SkipWhileと TakeWhile メソッド

次は、SkipWhileと TakeWhileメソッドについてみていきます。このメソッドは Func<T, bool>

のデリゲートを受け取ります。このデリゲートが trueを返す間は Skipしたり Takeをします。動

作確認のためのコードを下記に示します。

// 値の発行元になる Subject

var s = new Subject<int>();

// 発行される値が 5より小さい間はスキップして 10より小さい間は通過させる

s.SkipWhile(i => i < 5).TakeWhile(i => i < 10).Subscribe(

i => Console.WriteLine("OnNext({0})", i),

ex => Console.WriteLine("OnError({0})", ex.Message),

() => Console.WriteLine("OnCompleted()"));

// 1-20の値を流し込む

Console.WriteLine("1-20 OnNext start.");

Observable.Range(1, 20).ForEach(i => s.OnNext(i));

Console.WriteLine("1-20 OnNext end.");

Console.WriteLine();

5より小さい間はスキップして 10より小さい間は通過させるので 5,6,7,8,9が Subscribeまで通

知されます。実行結果を下記に示します。

1-20 OnNext start.

OnNext(5)

OnNext(6)

OnNext(7)

OnNext(8)

OnNext(9)

OnCompleted()

1-20 OnNext end.

この例も、9が発行された段階で値が発行されなくなるため OnCompletedが通知されていること

が確認できます。コードでは示しませんが、この例でも Repeatメソッドを組み合わせることで任

意の回数この挙動を繰り返すことが出来ます。

Page 62: Reactive extensions入門v0.1

55

4.3.4. SkipUntil と TakeUntil メソッド

次は LINQには定義されていない Reactive Extensions固有のメソッドの SkipUntilと TakeUntil

メソッドについてみていきます。SkipUntilと TakeUntilメソッドのシグネチャを下記に示します。

public IObservable<T> SkipUntil<T, TOther>(IObservable<TOther> other);

public IObservable<T> TakeUntil<T, TOther>(IObservable<TOther> other);

どちらも引数に IObservable<TOther>を受け取ります。SkipUntilは引数で渡された

IObservable<TOther>から値が発行されるまで、値のスキップを行います。TakeUntilは引数で

渡された IObservable<TOther>から値が発行されるまで、値を通過させます。コード例を下記

に示します。

// 値の発行元になる Subject

var s = new Subject<int>();

// 値のスキップを辞めるきっかけ

var startTrigger = new Subject<Unit>();

// 値を後続に流すことを辞めるきっかけ

var endTrigger = new Subject<Unit>();

// startTriggerの OnNextが呼ばれるまで値をスキップして endTriggerの OnNextが

// 呼ばれるまで後続に値を流す。

s.SkipUntil(startTrigger).TakeUntil(endTrigger).Subscribe(

i => Console.WriteLine("OnNext({0})", i),

ex => Console.WriteLine("OnError({0})", ex.Message),

() => Console.WriteLine("OnCompleted()"));

// 1-5の値を流し込む

Console.WriteLine("1-5 OnNext start.");

Observable.Range(1, 5).ForEach(i => s.OnNext(i));

Console.WriteLine("1-5 OnNext end.");

Console.WriteLine();

// startTriggerの OnNextを発行してから 1-5の値を流し込む

Console.WriteLine("startTrigger.OnNext called.");

startTrigger.OnNext(Unit.Default);

Console.WriteLine("1-5 OnNext start.");

Observable.Range(1, 5).ForEach(i => s.OnNext(i));

Console.WriteLine("1-5 OnNext end.");

Console.WriteLine();

Page 63: Reactive extensions入門v0.1

56

// endTriggerの OnNextを発行してから 1-5の値を流し込む

Console.WriteLine("endTrigger.OnNext called.");

endTrigger.OnNext(Unit.Default);

Console.WriteLine("1-5 OnNext start.");

Observable.Range(1, 5).ForEach(i => s.OnNext(i));

Console.WriteLine("1-5 OnNext end.");

Console.WriteLine();

コードは長いですがポントは、startTriggerが SkipUntilに渡す IObservable<T>で、

endTriggerが TakeUntilに渡す IObservable<T>になる点です。そして、startTriggerと

endTriggerの OnNextが呼ばれた際に発行された値が Subscribeに通知されるようになったり、

通知されなくなったりする点に注目してください。実行結果を下記に示します。

1-5 OnNext start.

1-5 OnNext end.

startTrigger.OnNext called.

1-5 OnNext start.

OnNext(1)

OnNext(2)

OnNext(3)

OnNext(4)

OnNext(5)

1-5 OnNext end.

endTrigger.OnNext called.

OnCompleted()

1-5 OnNext start.

1-5 OnNext end.

startTriggerの OnNextを呼ぶ前に発行した値は全てスキップされて、startTriggerの OnNext

を呼ぶと値が通知されて OnNext(1)~OnNext(5)までが表示されています。endTriggerの

OnNextを呼ぶと OnCompletedが呼ばれて終了状態になり、以降に発行された値は Subscribe

まで通知が届いていないことが確認できます。

4.3.5. SkipUntil と TakeUntil を使ったドラッグの処理

ここでは、SkipUntilと TakeUntilを使ってWPFゕプリケーションでマウスのドラッグを処理し

てみます。通常のゕプリケーションでは恐らく MouseDown, MouseUp, MouseMoveのベント

を処理して実現すると思います。MouseDownでフラグ変数を立ててマウスをキャプチャし、

MouseUpでフラグ変数を下げてマウスのキャプチャを解放します。そして、MouseMoveベン

トではフラグ変数を見てマウスボタンが押されているかどうか判定してドラッグ中の処理を記述

Page 64: Reactive extensions入門v0.1

57

します。このように単純なドラッグの処理を行うだけでも、フラグを使用したプログラムになって

しまいます。Reactive Extensionsを使うとベントを組み合わせて下記のように記述出来ます。

まず、MouseDown, MouseUp, MouseMoveのベントを FromEventメソッドを使って

IObservableに変換します。

// マウスダウン、マウスゕップ、マウスムーブの IObservableを作る

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);

ドラッグ処理は、マウスが押された状態でマウスが動いている間なので、SkipUntilで MouseMove

ベントをMouseDownが行われるまで読み飛ばし、TakeUntilでMouseUpが行われるまでTake

することで表しています。これだけだと、一度きりの処理なので Repeatメソッドを使って繰り返

し処理をするようにしています。コードを下記に示します。

var drag = mouseMove

// マウスムーブをマウスダウンまでスキップ。マウスダウン時にマウスをキャプチャ

.SkipUntil(mouseDown.Do(_ => this.CaptureMouse()))

// マウスゕップが行われるまで Take。マウスゕップでマウスのキャプチャをリリース

.TakeUntil(mouseUp.Do(_ => this.ReleaseMouseCapture()))

// ドラッグが終了したタミングで Completedを表示

.Finally(() => textBlock.Text = "Completed")

// これを繰り返す

.Repeat();

TakeUntilの後に Finallyメソッドという使ったことの無いメソッドを使用していますが、このメ

ソッドは IObservable<T>のシーケンスが終了したタミングでしたい処理を記載することが出

来るメソッドです。こうすることで、一回のドラッグが終了したタミングで TextBlockに

Completedと表示するようにしています。その他に、mouseDownとmouseUpで使用している

Doメソッドは、純粋に IObservable<T>のシーケンスに値が流れてきた時に処理を行うためのメ

ソッドです。ここで、マウスのキャプチャとキャプチャのリリースを行っています。

Page 65: Reactive extensions入門v0.1

58

最後に、この dragを Subscribeしてドラッグ中の処理を記述します。

// ドラッグ中は、ベント引数から座標を取り出して表示用に整えて TextBlockに設定

drag.Select(e => e.GetPosition(null))

.Select(p => string.Format("X: {0}, Y:{1}", p.X, p.Y))

.Subscribe(s => textBlock.Text = s);

ベント引数から座標を取り出し、座標を表示用文字列に整形して、TextBlockに表示させていま

す。因みに、上記の処理はWindow1.xaml.csのコンストラクタに記載しています。Window1.xaml

の XAMLは下記のようになっています。

<Window x:Class="DragSample.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 Name="layoutRoot">

<TextBlock Name="textBlock" />

</Grid>

</Window>

4.3.5.1. 実行結果

このプログラムを実行すると、下記のように何も表示されないウゖンドウが表示されます。

このウゖンドウ上でドラッグを行うと下図のようにドラッグしている箇所の座標がリゕルタム

で表示されます。

Page 66: Reactive extensions入門v0.1

59

ドラッグを終了すると下図のように Completedと表示されます。

このように SkipUntilと TakeUntilと Repeat, Selectなどのこれまで紹介してきたメソッドを組

み合わせることで通常はフラグなどを使用して記載する処理を非常にシンプルに記載できました。

最後に、MainWindow.xaml.csのコードの全体を下記に示します。

namespace DragSample

{

using System;

using System.Reactive.Linq;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Shapes;

/// <summary>

/// MainWindow.xaml の相互作用ロジック

/// </summary>

public partial class MainWindow : Window

Page 67: Reactive extensions入門v0.1

60

{

public MainWindow()

{

InitializeComponent();

// マウスダウン、マウスゕップ、マウスムーブの IObservableを作る

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);

var drag = mouseMove

// マウスムーブをマウスダウンまでスキップ。マウスダウン時にマウスをキャプチャ

.SkipUntil(mouseDown.Do(_ => this.CaptureMouse()))

// マウスゕップが行われるまで Take。マウスゕップでマウスのキャプチャをリリース

.TakeUntil(mouseUp.Do(_ => this.ReleaseMouseCapture()))

// ドラッグが終了したタミングで Completedを表示

.Finally(() => textBlock.Text = "Completed")

// これを繰り返す

.Repeat();

// ドラッグ中は、ベント引数から座標を取り出して表示用に整えて TextBlockに設定

drag.Select(e => e.GetPosition(null))

.Select(p => string.Format("X: {0}, Y:{1}", p.X, p.Y))

.Subscribe(s => textBlock.Text = s);

Page 68: Reactive extensions入門v0.1

61

}

}

}

4.3.6. SkipLastと TakeLastメソッド

ここでは、SkipLastメソッドと TakeLastメソッドについて説明します。SkipLastメソッドと

TakeLastメソッドは名前が示す通り IObservable<T>のシーケンスの最後から指定した数の値

を Skipしたり Takeしたりするメソッドです。メソッドのシグネチャは下記に示す通り、どちら

も何個の値を Skipするのか Takeするのかを指定する数を渡します。

public static IObservable<T> SkipLast<T>(this IObservable<T> source, int count);

public static IObservable<T> TakeLast<T>(this IObservable<T> source, int count);

SkipLastメソッドの使用例を下記に示します。

Observable

// 1~10の値を発行する

.Range(1, 10)

// 最後 3つを Skip

.SkipLast(3)

// 購読して表示

.Subscribe(

i => Console.WriteLine("OnNext({0})", i),

() => Console.WriteLine("OnCompleted()"));

このコードは、1~10の値を発行して最後の 3つの値を Skipしています。実行結果を以下に示し

ます。8,9,10の最後の 3つの値が表示されていないことが確認できます。

OnNext(1)

OnNext(2)

OnNext(3)

OnNext(4)

OnNext(5)

OnNext(6)

OnNext(7)

OnCompleted()

次に TakeLastメソッドの使用例を下記に示します。

Observable

// 1~10の値を発行する

.Range(1, 10)

// 最後 3つを Take

Page 69: Reactive extensions入門v0.1

62

.TakeLast(3)

// 購読して表示

.Subscribe(

i => Console.WriteLine("OnNext({0})", i),

() => Console.WriteLine("OnCompleted()"));

このコードは、1~10の値を発行して最後の 3つを拾っています。実行結果を以下に示します。

SkipLastの例では飛ばされていた 8,9,10が表示されていることが確認できます。

OnNext(8)

OnNext(9)

OnNext(10)

OnCompleted()

このように、SkipLastメソッドと TakeLastメソッドを使うことで IObservable<T>のシーケン

スの最後を起点にして Skipしたり Takeをすることが出来ます。そのため、永遠に終わらない

IObservable<T>のシーケンスに対して、このメソッドを呼び出すと永遠に結果が返ってこない

ので注意が必要です。

4.4. Do メソッド

ここでは、Doメソッドについて説明します。Doメソッドは IObservable<T>から発行された値

を受けて処理を行うだけのメソッドです。4.3.5の Drag処理でも使用しましたが、要素を受け取

って処理を行い IObservable<T>の要素に対しては何も加工を行ったりしません。コード例を下

記に示します。

var subject = new Subject<int>();

subject

// 途中に処理を挟む

.Do(i => Console.WriteLine("Do : {0}", i))

// 購読(購読しないと OnNextをしても値が流れないね)

.Subscribe(i => Console.WriteLine("OnNext : {0}", i));

// 値の発行

subject.OnNext(1);

subject.OnNext(2);

subject.OnNext(3);

このコードを実行すると、Subscribeで値を購読している処理の前に Doの処理が行われているこ

とが確認出来ます。

Do : 1

OnNext : 1

Do : 2

Page 70: Reactive extensions入門v0.1

63

OnNext : 2

Do : 3

OnNext : 3

Doメソッドには、OnNextに対応する処理だけではなく OnErrorや OnCompleted時の処理を指

定することも出来ます。このオーバーロードは Subscribeと同じで OnErrorには

Action<Exception>を、OnCompletedには引数なしの Actionを渡します。OnErrorの場合のコ

ード例を下記に示します。

var subject = new Subject<int>();

subject

// 途中に処理を挟む

.Do(

i => Console.WriteLine("Do : OnNext : {0}", i),

ex => Console.WriteLine("Do : OnError : {0}", ex),

() => Console.WriteLine("Do : OnCompleted"))

// 購読(購読しないと OnNextをしても値が流れないね)

.Subscribe(

i => Console.WriteLine("OnNext : {0}", i),

ex => Console.WriteLine("OnError : {0}", ex),

() => Console.WriteLine("OnCompleted"));

// 値の発行

subject.OnNext(1);

subject.OnError(new Exception());

実行結果を下記に示します。

Do : OnNext : 1

OnNext : 1

Do : OnError : System.Exception: 種類 'System.Exception' の例外がスローされました。

OnError : System.Exception: 種類 'System.Exception' の例外がスローされました。

最後に OnCompletedの場合のコード例を下記に示します。

var subject = new Subject<int>();

subject

.Do(

i => Console.WriteLine("Do : OnNext : {0}", i),

ex => Console.WriteLine("Do : OnError : {0}", ex),

() => Console.WriteLine("Do : OnCompleted"))

// 購読(購読しないと OnNextをしても値が流れないね)

.Subscribe(

i => Console.WriteLine("OnNext : {0}", i),

Page 71: Reactive extensions入門v0.1

64

ex => Console.WriteLine("OnError : {0}", ex),

() => Console.WriteLine("OnCompleted"));

// 値の発行

subject.OnNext(1);

subject.OnCompleted();

実行結果を下記に示します。

Do : OnNext : 1

OnNext : 1

Do : OnCompleted

OnCompleted

このように、Doメソッドを使うと IObservable<T>のシーケンスを処理する途中に任意のゕクシ

ョンを実行できます。

4.4.1. Do メソッド使用時の注意点

Doメソッドは Reactive Extensionsの処理の中に任意のゕクションを実行できるという便利なメ

ソッドですが、本来は外部に対して副作用を起こさない Reactive Extensionsの処理の中で副作

用を起こすためのメソッドになります。そのため、Doメソッドの利用は必要最低限にとどめてく

ださい。Doメソッド以外の代替メソッドがある場合はそれを使用するように心がけましょう。ま

た、複数の Doメソッドでフラグ変数を共有して挙動を変えるなど、動作が複雑化してきた場合は、

一度立ち止まり本当にそれが必要か考えるようにすることをお勧めします。

4.5. エラー処理関連のメソッド

ここでは、IObservable<T>のシーケンス内で例外が発生したときの挙動に関わるメソッドにつ

いて紹介します。

4.5.1. Catch メソッド

ここでは、例外処理を行うための Catchメソッドについて紹介します。例外処理については、こ

れまでも OnErrorで処理をするという方法をとってきましたが、Catchメソッドを使うことでエ

ラーの際にリトラを行ったり、任意の値を後ろに対して流すといったことが出来ます。

Catchメソッドには、いくつかオーバーラドがありますが一番単純なものは、IObservable<T>

を引数に渡すものになります。メソッドのシグネチャを下記に示します。

IObservable<TSource> Catch<TSource>(IObservable<TSource> )

使用例を下記に示します。

var source = new Subject<string>();

source

// sourceから例外が発生したら Errorという文字列を後続へ流す

Page 72: Reactive extensions入門v0.1

65

.Catch(Observable.Return("Error"))

// 購読

.Subscribe(

s => Console.WriteLine("OnNext {0}", s),

ex => Console.WriteLine("OnError {0}", ex),

() => Console.WriteLine("OnCompleted"));

// 2つ値を発行したあと、例外を流して、その後にもう1つ値を発行する。

source.OnNext("A");

source.OnNext("B");

source.OnError(new Exception("例外"));

source.OnNext("C");

実行結果を下記に示します。

OnNext A

OnNext B

OnNext Error <- 例外が発生したので Errorという文字列が Subscribeにわたってきている

OnCompleted <- その後、正常終了。

実行結果からもわかるとおり、Catchメソッドに Observable.Return(“Error”)を渡すことで、エ

ラー時には Errorという文字列を後続に対して流すようにしています。ここでは固定の値を返して

いますが IObservable<T>(この場合は IObservable<string>)であれば何を返してもいいので、

ここまでに紹介してきたメソッドを使って非同期処理を呼び出すことも、別のベントの発火を待

つことも可能です。

また、この Catchメソッドに渡す IObservable<T>が終了したタミングで OnCompletedが呼

ばれることも上記サンプルから確認できます。そのためエラー時に必ず終了させるといった処理を

行いたい場合は、空の IObservable<T>を返します。コード零を下記に示します。

var source = new Subject<string>();

source

// エラーが起きたら終了する

.Catch(Observable.Empty<string>())

// 購読

.Subscribe(

s => Console.WriteLine("OnNext {0}", s),

ex => Console.WriteLine("OnError {0}", ex),

() => Console.WriteLine("OnCompleted"));

// 2つ値を発行したあと、例外を流して、その後にもう1つ値を発行する。

source.OnNext("A");

Page 73: Reactive extensions入門v0.1

66

source.OnNext("B");

source.OnError(new Exception("例外"));

source.OnNext("C");

実行結果を下記に示します。

OnNext A

OnNext B

OnCompleted

Observable.Returnを使用した時とは違い、エラーが発生したタミングで OnCompletedが呼

ばれていることがわかります。

Catchメソッドには、上記以外のシグネチャのメソッド以外に Func<TException,

IObservable<TSource>>の形式のデリゲートを渡すオーバーラドがあります。このオーバー

ラドを使うと通常の例外処理の catch句のように型指定で例外時の処理を振り分けることが出

来ます。コード例を下記に示します。

var source = new Subject<string>();

source

// ArgumentException発生時には ArgumentExceptionからの復帰という文字列を後続に流す

.Catch((ArgumentException ex) => Observable.Return("ArgumentExceptionから復帰"))

// NullReferenceException発生時には何もせず終了

.Catch((NullReferenceException ex) => Observable.Empty<string>())

// 購読

.Subscribe(

s => Console.WriteLine("OnNext {0}", s),

ex => Console.WriteLine("OnError {0}", ex),

() => Console.WriteLine("OnCompleted"));

// 2つ値を発行したあと、例外を流して、その後にもう1つ値を発行する。

source.OnNext("A");

source.OnNext("B");

source.OnError(new ArgumentException("例外")); // ## 1

// source.OnError(new NullReferenceException("例外")); // ## 2

// source.OnError(new Exception("例外")); // ## 3

source.OnNext("C");

上記の例では、2つの Catchメソッドで ArgumentExceptionと NullReferenceExceptionの場

合のエラー処理を行っています。ArgumentException発生時には ArgumentExceptionが発生し

た旨を文字列で後続に対して流すようにしています。NullReferenceExceptionの場合は、即座に

シーケンスを終了するようにしています。コードの後半の## 1, ## 2, ## 3のコメントのある

行のどれか1行だけ有効にした状態で実行することで、Catchメソッドの動作を確認できます。ま

Page 74: Reactive extensions入門v0.1

67

ず、## 1の行をコメントゕウトして ArgumentExceptionを発生させた場合の実行例を下記に示

します。

OnNext A

OnNext B

OnNext ArgumentExceptionから復帰

OnCompleted

期待したとおり、ArgumentExceptionから復帰という文字列が OnNextにわたってきていること

が確認できます。次に、## 1の行をコメントにして## 2の行のコメントを外して

NullReferenceExceptionを発生させたケースの実行例を下記に示します。

OnNext A

OnNext B

OnCompleted

こちらは、Observable.Empty<string>()を返しているため、例外が発生したタミングで

OnCompletedが呼ばれていることがわかります。最後に、## 3のコメントを外して Exception

を発生させたケースの実行例を下記に示します。

OnNext A

OnNext B

OnError System.Exception: 例外

この場合は、どの Catchメソッドでも処理されないため Subscribeの OnErrorまで例外が伝搬さ

れています。このように Reactive Extensionsでは例外に対して複数の対処方法を用意していま

す。この中から、状況に応じて最適な例外処理選択して使用します。

4.5.2. Finally メソッド

Catchメソッドの次は、Finallyメソッドの紹介を行います。Finallyメソッドは名前の通り例外処

理の finally句と同じ動きをします。Reactive Extensionsの IObservable<T>を起点とする一連

のシーケンスの処理が終わったタミングで呼び出される処理を記述することが出来ます。引数に

は Actionを渡すだけのシンプルなシグネチャです。

IObservable<TSource> Finally(Action action)

まずは、正常にシーケンスの処理が終わる場合のコード例を下記に示します。

var source = new Subject<string>();

source

// 終了時に必ず呼ばれる処理

.Finally(() => Console.WriteLine("Finally"))

// 購読

.Subscribe(

s => Console.WriteLine("OnNext {0}", s),

ex => Console.WriteLine("OnError {0}", ex),

Page 75: Reactive extensions入門v0.1

68

() => Console.WriteLine("OnCompleted"));

// 2つ値を発行したあと、終了する。

source.OnNext("A");

source.OnNext("B");

source.OnCompleted();

実行結果を下記に示します。

OnNext A

OnNext B

OnCompleted

Finally

Finallyが、全ての処理の最後に呼ばれていることが確認出来ます。次に、Catchメソッドを使用

して例外を処理したケースのコードを下記に示します。

var source = new Subject<string>();

source

// sourceから例外が発生したら Errorという文字列を後続へ流す

.Catch(Observable.Return("Error"))

// 終了時に必ず呼ばれる処理

.Finally(() => Console.WriteLine("Finally"))

// 購読

.Subscribe(

s => Console.WriteLine("OnNext {0}", s),

ex => Console.WriteLine("OnError {0}", ex),

() => Console.WriteLine("OnCompleted"));

// 2つ値を発行したあと、例外を発生させる。

source.OnNext("A");

source.OnNext("B");

source.OnError(new Exception("例外"));

実行結果を下記に示します。

OnNext A

OnNext B

OnNext Error

OnCompleted

Finally

Page 76: Reactive extensions入門v0.1

69

この場合も Catchメソッドでエラーから復帰しているため、OnCompletedの後に Finallyが呼ば

れていることが確認できます。次に、Catchメソッドを使用せずに Subscribeの OnErrorで例外

処理をした場合の動作確認をするコードを下記に示します。

var source = new Subject<string>();

source

// 終了時に必ず呼ばれる処理

.Finally(() => Console.WriteLine("Finally"))

// 購読

.Subscribe(

s => Console.WriteLine("OnNext {0}", s),

ex => Console.WriteLine("OnError {0}", ex),

() => Console.WriteLine("OnCompleted"));

// 2つ値を発行したあと、例外を発生させる。

source.OnNext("A");

source.OnNext("B");

source.OnError(new Exception("例外"));

実行結果を下記に示します。

OnNext A

OnNext B

OnError System.Exception: 例外

Finally

この場合も、OnErrorなどの Subscribeで行われる処理の後に Finallyで指定した処理が実行され

ていることがわかります。最後に、OnErrorでも Catchメソッドでも例外を処理しなかった場合

の動作を確認するコードを下記に示します。

var source = new Subject<string>();

var subscriber = source

// 終了時に必ず呼ばれる処理

.Finally(() => Console.WriteLine("Finally"))

// 購読(エラー処理無し)

.Subscribe(

s => Console.WriteLine("OnNext {0}", s));

// 2つ値を発行したあと、例外を発生させる。

source.OnNext("A");

source.OnNext("B");

try

Page 77: Reactive extensions入門v0.1

70

{

// Reactive Extensionsのシーケンスの処理内で例外が処理されないため例外がスローされる

source.OnError(new Exception("例外"));

}

catch

{

// 例外が発生した場合はログを出して握りつぶす

Console.WriteLine("catch句");

}

// 購読を解除する

Console.WriteLine("subscriber.Dispose()");

subscriber.Dispose();

今回は、OnErrorでも Catchメソッドでも例外を処理していないため source.OnError(new

Exception(“例外”))の部分で例外が発生します。そのため try catchで例外処理を記載しています。

また、例外処理のあとで、Subscribeしたものを Disposeして購読を解除しています。このコー

ドの実行結果を下記に示します。

OnNext A

OnNext B

catch句

subscriber.Dispose()

Finally

上記の実行結果からわかるように、未処理の例外がある場合は、何もしないと Finallyメソッドで

渡したゕクションが実行されません。明示的にコードで Disposeをすることで Finallyで渡したゕ

クションが実行されます。

4.5.3. OnErrorResumeNext メソッド

ここでは、OnErrorResumeNextメソッドについて説明します。このメソッドは IObservable<T>

のシーケンスで例外が発生した際に、どのように IObservable<T>のシーケンスを再開するのか

を指定します。IObservable<T>の拡張メソッドとして定義されているものは「4.5.1 Catchメソ

ッド」で説明したものと同じように、例外発生時に別の IObservable<T>で処理を続けるという

ことが出来ます。

コード例を下記に示します。

Observable

// 例外を出す

.Throw<string>(new Exception())

// OnErrorResumeNextでエラーになったときの代わりを指定しておく

.OnErrorResumeNext(Observable.Return("OK"))

// 購読

Page 78: Reactive extensions入門v0.1

71

.Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

ex => Console.WriteLine("OnError: {0}", ex),

() => Console.WriteLine("OnCompleted"));

最初に Throwメソッドを使って例外を発生させています。それに対して OnErrorResumeNext

メソッドを使って OKという文字列を流し込んでいます。実行結果を以下に示します。

OnNext: OK

OnCompleted

Throwメソッドで発生された例外は、Subscribeまで届いていないことが確認できます。このよ

うに例外発生時に、別の IObservable<T>で処理を再開することが出来ます。因みに Catchメソ

ッドでは例外の種類を指定できましたが OnErrorResumeNextでは例外の種類を指定できないの

で、この点だけを見ると Catchメソッドのほうが柔軟な対応が出来ます。

OnErrorResumeNextは、IObservable<T>の拡張メソッドの他に

IEnumerable<IObservable<T>>の拡張メソッドや、引数に params IObservable<T>[]を受

け取るオーバーロードが定義されています。メソッドのシグネチャを下記に示します。

public static IObservable<T> OnErrorResumeNext<T>(this IEnumerable<IObservable<T>> sources);

public static IObservable<T> OnErrorResumeNext<T>(params IObservable<T>[] sources);

このメソッドを使うと、最初の IObservable<T>で例外が発生したら、次の IObservable<T>で

再開して、そこでも例外が発生したら、次の IObservable<T>で再開して・・・ということが実

現できます。コード例を下記に示します。

// 4番目に OK

new[] { "NG", "Error", "Abort", "OK" }

// ンデックスと値のペゕに変換

.Select((s, i) => new { index = i, value = s })

// OK以外は例外を飛ばす IO<string>を返す(IEnumerable<IObservable<T>>へ変換)

.Select(s => s.value != "OK" ?

Observable.Throw<string>(new Exception(s.ToString())) :

Observable.Return(s.ToString()))

.OnErrorResumeNext()

// 購読

.Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

ex => Console.WriteLine("OnError: {0}", ex),

() => Console.WriteLine("OnCompleted"));

多少複雑ですが、{“NG”, “Error”, “Abort”, “OK”}という文字列の配列から、

IEnumerable<IObservable<T>>へ変換して OnErrorResumeNextを呼び出しています。内容

Page 79: Reactive extensions入門v0.1

72

としては”OK”という文字列以外が渡ってきた場合は Observable.Throwメソッドを使って例外を

発生させる IObservable<T>を作成しています。実行結果を以下に示します。

OnNext: { index = 3, value = OK }

OnCompleted

NGや Error, Abortの文字列から生成される例外は全て無視して OKが Subscribeに渡ってきて

いることが確認できます。動作確認のため OKの文字列の場所を下記のように 2番目にくるように

書き直します。

// 2番目に OK

new[] { "NG", "OK", "Abort", "Error" }

// ンデックスと値のペゕに変換

.Select((s, i) => new { index = i, value = s })

// OK以外は例外を飛ばす IO<string>を返す(IEnumerable<IObservable<T>>へ変換)

.Select(s => s.value != "OK" ?

Observable.Throw<string>(new Exception(s.ToString())) :

Observable.Return(s.ToString()))

.OnErrorResumeNext()

// 購読

.Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

ex => Console.WriteLine("OnError: {0}", ex),

() => Console.WriteLine("OnCompleted"));

実行結果を以下に示します。

OnNext: { index = 1, value = OK }

OnCompleted

今度は indexが 1になっていることが確認できます。全て例外で終わった場合(このプログラム

例では最初の文字列の配列に OKが無い場合)は以下のように OnCompletedになります。

OnCompleted

これまでのサンプルでは OnErrorResumeNextの挙動を確認するために単純なプログラムを書い

ていましたが、このメソッドを使うと例えば、プラマリのサーバに対して非同期にデータを要求

して例外が発生した場合にはセカンダリのサーバに対してデータを要求するというプログラムを

書くことができます。

4.5.4. Retry メソッド

ここでは Retryメソッドについて説明します。Retryメソッドは名前の通りエラーが起きた場合に、

繰り返し同じ処理を行うメソッドです。メソッドのオーバーロードには、成功するまで無限に繰り

返しリトラをするものと、引数でリトラ回数を指定するものがあります。メソッドのシグネチ

ャを下記に示します。

Page 80: Reactive extensions入門v0.1

73

public static IObservable<T> Retry<T>(this IObservable<T> source);

public static IObservable<T> Retry<T>(this IObservable<T> source, int retryCount);

まず、無限に繰り返すメソッドのオーバーロードを使ったコード例を下記に示します。

var retryCount = 0;

Observable

// retryCountが 3になるまでエラーになる

.Create<string>(o =>

{

Console.WriteLine("Create method called: {0}", retryCount);

if (retryCount == 3)

{

o.OnNext(retryCount.ToString());

o.OnCompleted();

return Disposable.Empty;

}

retryCount++;

o.OnError(new InvalidOperationException(retryCount.ToString()));

return Disposable.Empty;

})

// 成功するまでリトラする

.Retry()

// 購読

.Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

ex => Console.WriteLine("OnError: {0}", ex),

() => Console.WriteLine("OnCompleted"));

Createメソッドで 3回目までエラーになる IObservable<string>を作成しています。それに対し

て Retryメソッドを呼び出して、結果を表示しています。実行結果を以下に示します。

Create method called: 0

Create method called: 1

Create method called: 2

Create method called: 3

OnNext: 3

OnCompleted

Page 81: Reactive extensions入門v0.1

74

Createメソッドが 4回呼ばれてエラーではない値が発行されると Subscribeまで値が渡っている

ことが確認できます。このようにエラーが起きても何度もリトラします。

次にリトラ回数を指定した場合の動作を示すコード例を下記に示します。

var retryCount = 0;

Observable

// retryCountが 3になるまでエラーになる

.Create<string>(o =>

{

Console.WriteLine("Create method called: {0}", retryCount);

if (retryCount == 3)

{

o.OnNext(retryCount.ToString());

o.OnCompleted();

return Disposable.Empty;

}

retryCount++;

o.OnError(new InvalidOperationException(retryCount.ToString()));

return Disposable.Empty;

})

// 2回リトラする

.Retry(2)

// 購読

.Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

ex => Console.WriteLine("OnError: {0}", ex),

() => Console.WriteLine("OnCompleted"));

先ほどと、ほぼ同じコードですが Retryメソッドの引数で 2回までしかリトラをしないように

指定しています。このコードの実行結果を以下に示します。

Create method called: 0

Create method called: 1

OnError: System.InvalidOperationException: 2

実行結果からわかるように 2回目のリトラでも例外が発生しているため、SubscribeのOnError

が呼び出されています。おそらく、現実的にはリトラ回数を指定するオーバーロードを使うこと

が多くなると思います。

Page 82: Reactive extensions入門v0.1

75

4.6. IObservable<T>の値を収集するメソッド

ここでは、IObservable<T>の値を収集するメソッドについて説明します。

4.6.1. ToArrayメソッド

まず、最初に IObservable<T>の値を収集して IObservable<T[]>へ変換する ToArrayメソッド

について説明します。これは、単純な Subject<T>を使ったシーケンスに対して適用すると

OnNext~OnCompletedまでの値を収集して配列にします。コード例を下記に示します。

var s = new Subject<int>();

// IO<T> -> IO<T[]>へ変換して購読

s.ToArray().Subscribe(array =>

{

// 内容表示

Console.WriteLine("start array dump");

foreach (var i in array)

{

Console.WriteLine(" array value : {0}", i);

}

});

// 値の発行から Completed

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(2)");

s.OnNext(2);

Console.WriteLine("OnNext(3)");

s.OnNext(3);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

実行結果を下記に示します。

OnNext(1)

OnNext(2)

OnNext(3)

OnCompleted()

start array dump

array value : 1

array value : 2

Page 83: Reactive extensions入門v0.1

76

array value : 3

実行結果からもわかる通り、1つの IObservable<T>シーケンスをまとめて配列にして

IObservable<T[]>へ変換しています。

4.6.2. ToDictionary メソッド

この ToDictionaryメソッドも、ToArrayと同じような動きをします。IObservable<T>から

IObservable<TKey, T>への変換を行います。TKeyは ToDictionaryのメソッド引数に Keyを選

択するラムダ式を渡すことで指定します。コード例を下記に示します。

var s = new Subject<Tuple<string, int>>();

// IO<T> -> IO<IDictionary<TKey, T>>へ変換して購読

// Keyを選択するラムダ式を渡す。

s.ToDictionary(t => t.Item1).Subscribe(dict =>

{

Console.WriteLine("one : {0}", dict["one"]);

Console.WriteLine("two : {0}", dict["two"]);

Console.WriteLine("three : {0}", dict["three"]);

});

// 値の発行から Completed

Console.WriteLine("OnNext(one)");

s.OnNext(Tuple.Create("one", 1));

Console.WriteLine("OnNext(two)");

s.OnNext(Tuple.Create("two", 2));

Console.WriteLine("OnNext(three)");

s.OnNext(Tuple.Create("three", 3));

Console.WriteLine("OnCompleted()");

s.OnCompleted();

実行結果を下記に示します。

OnNext(one)

OnNext(two)

OnNext(three)

OnCompleted()

one : (one, 1)

two : (two, 2)

three : (three, 3)

Tupleを発行して、Tupleの Item1をキーにして IDictionaryを作成しています。実行結果から

も、one, two, threeなどの Tupleの最初の要素がキーになっていることが確認できます。

Page 84: Reactive extensions入門v0.1

77

4.6.3. ToListメソッド

ToListメソッドは ToArrayメソッドと、ほぼ同様の動きを行います。ToArrayが

IObservable<T[]>を孵すのに B対して ToListメソッドハ IObservable<ILIST<T>>を返

します。コード例を下記に示します。

var s = new Subject<int>();

// IO<T> -> IO<IList<T>>へ変換

s.ToList().Subscribe(list =>

{

// 値を表示

foreach (var i in list)

{

Console.WriteLine("value : {0}", i);

}

});

// 値の発行から Completed

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(2)");

s.OnNext(2);

Console.WriteLine("OnNext(3)");

s.OnNext(3);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

実行結果を下記に示します。

OnNext(1)

OnNext(2)

OnNext(3)

OnCompleted()

value : 1

value : 2

value : 3

ToArrayと同様に OnNext~OnCompletedまでの間の値を格納した IList<T>が作成されている

ことが確認できます。

Page 85: Reactive extensions入門v0.1

78

4.6.4. ToLookup メソッド

次に、ToLookupメソッドについて説明します。これは ToDictionaryメソッドと同様にキーを選

択するラムダ式を渡します。そうすると、キー値でグルーピングされた

IObservable<ILookup<TKey, T>>が返されます。コード例を下記に示します。

var s = new Subject<Tuple<string, string>>();

// IO<T>から IO<ILookup<TKey, T>>へ変換

// Keyを選択するラムダ式を渡す

s.ToLookup(t => t.Item1).Subscribe(l =>

{

// グループ単位に表示

foreach (var g in l)

{

Console.WriteLine("Key : {0}", g.Key);

foreach (var i in g)

{

Console.WriteLine(" item : {0}", i);

}

}

});

Console.WriteLine("OnNext(group A)");

s.OnNext(Tuple.Create("group A", "taro"));

s.OnNext(Tuple.Create("group A", "jiro"));

Console.WriteLine("OnNext(group B)");

s.OnNext(Tuple.Create("group B", "foo"));

s.OnNext(Tuple.Create("group B", "hoge"));

s.OnNext(Tuple.Create("group B", "bar"));

Console.WriteLine("OnCompleted()");

s.OnCompleted();

実行結果を下記に示します。

OnNext(group A)

OnNext(group B)

OnCompleted()

Key : group A

Page 86: Reactive extensions入門v0.1

79

item : (group A, taro)

item : (group A, jiro)

Key : group B

item : (group B, foo)

item : (group B, hoge)

item : (group B, bar)

IObservable<T>から発行された値が Key 値ごとにグルーピングされていることが確認できます。

4.6.5. MaxメソッドとMin メソッドと Averageメソッド

ここでは、IObservable<T>のシーケンスから最大、最小、平均の値を返す Maxメソッド、Min

メソッド、Averageメソッドを説明します。この 3つのメソッドは、名前の通りそれぞれ最大値、

最小値、平均値のように単一の値を返す動作をしますが、これまでに説明した ToArrayなどのメ

ソッドと同様に戻り値が IObservable<T>となっています。そのため、他のメソッドと同様に

IObservable<T>の一連のメソッドチェンに対してシームレスに統合することが出来ます。こ

れらのメソッドの使用例を下記に示します。

var s = new Subject<int>();

// 最大値を求めて表示

s.Max().Subscribe(max =>

{

Console.WriteLine("Max {0}", max);

},

() => Console.WriteLine("Max Completed"));

// 最小値を求めて表示

s.Min().Subscribe(min =>

{

Console.WriteLine("Min {0}", min);

},

() => Console.WriteLine("Min Completed"));

// 平均を求めて表示

s.Average().Subscribe(avg =>

{

Console.WriteLine("Average {0}", avg);

},

() => Console.WriteLine("Average Completed"));

Page 87: Reactive extensions入門v0.1

80

// 値の発行~完了通知

Console.WriteLine("OnNext(1-3)");

s.OnNext(1);

s.OnNext(2);

s.OnNext(3);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

実行結果を下記に示します。

OnNext(1-3)

OnCompleted()

Max 3

Max Completed

Min 1

Min Completed

Average 2

Average Completed

1~3の値を発行しているので、最大値が 3、最小値が 1、平均が 2という結果になっていること

が確認できます。Maxメソッドや Minメソッドや Averageメソッドには数値を表す型と、それら

の型の nullable型に対応するオーバーロードが定義されています。Maxと Minは、その型の最大

値と最小値、Averageは double型で平均値の IObservableを返します。上記のコード例では、

IObservable<int>に対してメソッドを呼び出しているので int型の最大値と最小値、double型の

平均値を返しています。

また、Maxと Minメソッドには、独自の比較方法を指定するための IComparer<T>を受け取る

オーバーロードも定義されています。使用例を下記に示します。

// Tuple<int, int>の比較を行うクラス

class TupleIntIntComparer : IComparer<Tuple<int, int>>

{

// Item1 + Item2の結果で比較を行う

public int Compare(Tuple<int, int> x, Tuple<int, int> y)

{

if (x == y)

{

return 0;

}

if (x == null)

{

Page 88: Reactive extensions入門v0.1

81

return -1;

}

if (y == null)

{

return 1;

}

var xValue = x.Item1 + x.Item2;

var yValue = y.Item1 + y.Item2;

return Comparer<int>.Default.Compare(xValue, yValue);

}

}

// -----------------------------------------

// 最大値を求めて表示

s.Max(new TupleIntIntComparer()).Subscribe(max =>

{

Console.WriteLine("Max {0}", max);

},

() => Console.WriteLine("Max Completed"));

// 最小値を求めて表示

s.Min(new TupleIntIntComparer()).Subscribe(min =>

{

Console.WriteLine("Min {0}", min);

},

() => Console.WriteLine("Min Completed"));

// 値の発行~完了通知

Console.WriteLine("OnNext");

s.OnNext(Tuple.Create(1, 1));

s.OnNext(Tuple.Create(1, 2));

s.OnNext(Tuple.Create(3, 1));

Console.WriteLine("OnCompleted()");

s.OnCompleted();

実行結果を下記に示します。

Page 89: Reactive extensions入門v0.1

82

OnNext

OnCompleted()

Max (3, 1)

Max Completed

Min (1, 1)

Min Completed

このようにして、最大、最小を求める際の比較ロジックをカスタマズすることが出来ます。

4.6.6. MaxByメソッドとMinBy メソッド

ここでは、MaxByメソッドと MinByメソッドについて説明します。このメソッドは最大値、最小

値を求めるという意味では Maxメソッドと Minメソッドと同じですが、引数に最大と最小を求め

るためのキーとなる値を取得するための Func<T, TKey>型のデリゲートを受け取る点が異なり

ます。さらに、MaxByメソッドと MinByメソッドの戻り値は IObservable<T>ではなく

IObservable<IList<T>>のように複数の結果が返ることを想定したシグネチャになっています。

これは、デリゲートで取得したキー値が最小となる値がシーケンス内に複数存在する可能性がある

ためです。具体例を下記に示します。

var s = new Subject<Tuple<int, int>>();

// 最大値を求めて表示, 比較はタプルの Item1を使用する

s.MaxBy(t => t.Item1).Subscribe(max =>

{

foreach (var i in max)

{

Console.WriteLine("MaxBy {0}", i);

}

},

() => Console.WriteLine("MaxBy Completed"));

// 最小値を求めて表示, 比較はタプルの Item1を使用する

s.MinBy(t => t.Item1).Subscribe(min =>

{

foreach (var i in min)

{

Console.WriteLine("MinBy {0}", i);

}

},

() => Console.WriteLine("MinBy Completed"));

// 値の発行~完了通知

Page 90: Reactive extensions入門v0.1

83

Console.WriteLine("OnNext");

s.OnNext(Tuple.Create(1, 1));

s.OnNext(Tuple.Create(1, 2));

s.OnNext(Tuple.Create(3, 1));

Console.WriteLine("OnCompleted()");

s.OnCompleted();

実行結果を下記に示します。

OnNext

OnCompleted()

MaxBy (3, 1)

MaxBy Completed

MinBy (1, 1)

MinBy (1, 2)

MinBy Completed

MinByメソッドの結果が 2つあることが確認できます。これは Item1の値が 1になるタプルが(1,

1)と(1, 2)の 2種類あるためです。このようなケースに対応するために、IObservable<IList<T>>

のように IList<T>型を返す仕組みになっています。

また、ManByメソッドと MixByメソッドにも、比較方法をカスタマズできる IComparer<T>

ンターフェースを受け取るオーバーロードがあります。比較方法をカスタマズできる点以外は、

上記で示した MaxByメソッドと MinByメソッドと同様の動きをするため、コード例と実行結果

は省略します。

4.6.7. Count メソッドと LongCount メソッド

ここでは、Countメソッドと LongCountメソッドについて説明します。これは IObservable<T>

のシーケンスが完了するまでに発行された値の数を数えます。コード例を下記に示します。

var s = new Subject<int>();

// 数を数える

s.Count()

// 購読

.Subscribe(

i => Console.WriteLine("Count OnNext({0})", i),

() => Console.WriteLine("Count OnCompleted()"));

// 値の発行~完了通知

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(10)");

Page 91: Reactive extensions入門v0.1

84

s.OnNext(10);

Console.WriteLine("OnNext(100)");

s.OnNext(100);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

値を3つ発行して OnCompletedメソッドを呼び出しています。実行結果を下記に示します。

OnNext(1)

OnNext(10)

OnNext(100)

OnCompleted()

Count OnNext(3)

Count OnCompleted()

3つの値を発行したので 3が Subscribeで購読しているところに流れてきていることが確認でき

ます。LongCountは、流れてくる値の型が intから longになるだけなのでコード例と実行例につ

いては割愛します。

4.6.8. Any メソッド

次に Anyメソッドについて説明します。Anyメソッドは、引数で渡したデリゲートが trueを返す

要素が1つでもあれば trueを後続に流します。IObservable<T>のシーケンスが完了した時点で、

どれも trueにならなかった場合には falseを返します。コード例を下記に示します。

var s = new Subject<int>();

// どれかが 0以下かチェック

s.Any(i => i <= 0)

// 購読

.Subscribe(

i => Console.WriteLine("Any(i => i <= 0) OnNext({0})", i),

() => Console.WriteLine("Any(i => i <= 0) OnCompleted()"));

// どれかが偶数かチェック

s.Any(i => i % 2 == 0)

// 購読

.Subscribe(

i => Console.WriteLine("Any(i => i % 2 == 0) OnNext({0})", i),

() => Console.WriteLine("Any(i => i % 2 == 0) OnCompleted()"));

// 値の発行~完了通知

Console.WriteLine("OnNext(1)");

Page 92: Reactive extensions入門v0.1

85

s.OnNext(1);

Console.WriteLine("OnNext(10)");

s.OnNext(10);

Console.WriteLine("OnNext(100)");

s.OnNext(100);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

IObservable<T>に対して Anyメソッドを 2回呼び出しています。最初の Anyメソッドには渡っ

てきた値が 0以下かどうかを判定するデリゲートを渡しています。2つ目の Anyメソッドには渡

ってきた値が偶数かどうかを判定するデリゲートを渡しています。そして、1, 10, 100という 3

つの値を IObservable<T>のシーケンスに流して OnCompletedでシーケンスを終了させていま

す。実行結果を下記に示します。

OnNext(1)

OnNext(10)

Any(i => i % 2 == 0) OnNext(True)

Any(i => i % 2 == 0) OnCompleted()

OnNext(100)

OnCompleted()

Any(i => i <= 0) OnNext(False)

Any(i => i <= 0) OnCompleted()

注目すべき点は、偶数の値が流れてきた時点で、Trueが後続に流れている点です。OnNext(10)

の後にすぐログが出ていることが確認できます。一方、0以下の値は1つも IObservable<T>の

シーケンスに流していないためOnCompleted()が呼び出された後に Falseが流れていることが確

認できます。

4.6.9. All メソッド

次は、Allメソッドについて説明します。Allメソッドは Anyメソッドと異なり引数で渡したデリ

ゲートが全て Trueになるかどうかを確認します。コード例を下記に示します。

var s = new Subject<int>();

// 全てが偶数かどうかをチェック

s.All(i => i % 2 == 0)

// 購読

.Subscribe(

i => Console.WriteLine("All(i => i % 2 == 0) OnNext({0})", i),

() => Console.WriteLine("All(i => i % 2 == 0) OnCompleted()"));

// 全てが 1000以下かどうかをチェック

s.All(i => i <= 1000)

Page 93: Reactive extensions入門v0.1

86

// 購読

.Subscribe(

i => Console.WriteLine("All(i => i <= 1000) OnNext({0})", i),

() => Console.WriteLine("All(i => i <= 1000) OnCompleted()"));

// 値の発行~完了通知

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(10)");

s.OnNext(10);

Console.WriteLine("OnNext(100)");

s.OnNext(100);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

IObservable<T>のシーケンスに対して 2回 Allメソッドを呼び出しています。最初の呼び出しで

は、偶数かどうかを判定するデリゲートを渡しています。2つ目の呼び出しでは、1000以下かど

うかを判定するデリゲートを渡しています。この例も Allメソッドがどの時点で後続に対して結果

を流しているかがポントになります。実行結果を下記に示します。

OnNext(1)

All(i => i % 2 == 0) OnNext(False)

All(i => i % 2 == 0) OnCompleted()

OnNext(10)

OnNext(100)

OnCompleted()

All(i => i <= 1000) OnNext(True)

All(i => i <= 1000) OnCompleted()

偶数かどうかを判断するデリゲートを渡した Allメソッドは、OnNext(1)が呼び出された段階で

Falseになることが確定するので後続に Falseを流しています。1000以下かどうかを判断するデ

リゲートを渡した Allメソッドは 1, 10, 100の値では、どれも Falseにならないため

OnCompleted()が呼び出された時点でTrueになることが確定して、後続にTrueを流しています。

Anyメソッドもそうですが Allメソッドは、流れてきた値にたいしてリゕルタムに反応できる点

がとても Reactive Extensionsらしい特徴のメソッドになっています。

4.6.10. Aggregateメソッド

ここまでは、特定の収集・集計の方法を行うメソッドを見てきましたが、ここで紹介する

Aggregateメソッドは汎用的に IObservable<T>のシーケンスに対して収集・集計するメソッド

になります。Aggregateメソッドを使って IObservable<T>のシーケンスから最大の値を返す

Maxメソッドと同等のメソッドは、下記のようになります。

Page 94: Reactive extensions入門v0.1

87

var s = new Subject<int>();

// x, yから値の大きい方を返す

s.Aggregate((x, y) => x > y ? x : y).Subscribe(

i => Console.WriteLine("Aggregate OnNext({0})", i),

() => Console.WriteLine("Aggregate OnCompleted()"));

// 値の発行~完了通知

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(10)");

s.OnNext(10);

Console.WriteLine("OnNext(100)");

s.OnNext(100);

Console.WriteLine("OnNext(50)");

s.OnNext(50);

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

実行結果を下記に示します。

OnNext(1)

OnNext(10)

OnNext(100)

OnNext(50)

OnNext(1)

OnCompleted()

Aggregate OnNext(100)

Aggregate OnCompleted()

Aggregateメソッドは、2つの値を受け取り1つの値を返すデリゲートを引数に受け取ります。

2つの値のうちの第一引数が直前までの集計値で、第二引数が新たに IObservable<T>シーケン

スから発行された値になります。この2つの値を使って、任意の集計処理を行い集計結果を戻り値

として返します。IObservable<T>から新たな値が発行されると、直前までの集計値と、新たに

発行された値を使って集計処理を行います。これをシーケンスの完了まで繰り返し、シーケンスが

終了した時点の集計結果を後続に流します。動作確認をするためのコードを下記に示します。

var s = new Subject<int>();

Page 95: Reactive extensions入門v0.1

88

// IObservable<T>のシーケンスから渡される値の合計を求める

s.Aggregate((x, y) =>

{

// Aggregateの引数に渡されるデリゲートがどのように動くのか確認するためのログ

Console.WriteLine("log({0}, {1})", x, y);

// 今までの合計(x)と、新たな値(y)を足して新たな合計値として返す。

return x + y;

})

// 結果を購読

.Subscribe(

i => Console.WriteLine("Aggregate OnNext({0})", i),

() => Console.WriteLine("Aggregate OnCompleted()"));

// 値の発行~完了通知

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(10)");

s.OnNext(10);

Console.WriteLine("OnNext(100)");

s.OnNext(100);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

Aggregateメソッドの引数に渡しているデリゲート内で log(x, y)の形式で値を標準出力に出力し

て、どのようにメソッドが呼ばれているか確認できるようにしています。実行結果を下記に示しま

す。

OnNext(1)

OnNext(10)

log(1, 10)

OnNext(100)

log(11, 100)

OnCompleted()

Aggregate OnNext(111)

Aggregate OnCompleted()

注意して見てほしい点として、最初の OnNextでは Aggregateの引数で渡したデリゲートが実行

されない点です。2つ目の OnNextの呼び出しではじめて Aggregateの引数に渡したデリゲート

が実行されています。そして、最初の OnNextで渡した値の1が第一引数に、2つ目の OnNext

で渡した値の 10が第二引数に渡っていることが確認できます。次の OnNextで 100を発行する

Page 96: Reactive extensions入門v0.1

89

と、第一引数が 1 + 10の結果の 11で、第二引数に 100が渡されてデリゲートが実行されている

ことが、実行結果からわかります。そして、シーケンスが終了(OnCompleted)すると、Aggregate

の返す IObservable<T>から値が後続に流れて Subscribeしているところに 1 + 10 + 100の結

果である 111が渡ってきていることが確認できます。

この Aggregateメソッドには初期値を受け取るオーバーロードがあります。初期値を受け取るオ

ーバーロードでは、最初の OnNextで Aggregateに渡したデリゲートが呼ばれます。その時の第

一引数は、初期値で渡した値で、第二引数が、OnNextで発行された値になります。

コード例を下記に示します。

var s = new Subject<int>();

// IObservable<T>のシーケンスから渡される値の合計を求める

// ただし、初期値として 5を使用する

s.Aggregate(5, (x, y) =>

{

// Aggregateの引数に渡されるデリゲートがどのように動くのか確認するためのログ

Console.WriteLine("log({0}, {1})", x, y);

// 今までの合計(x)と、新たな値(y)を足して新たな合計値として返す。

return x + y;

})

// 結果を購読

.Subscribe(

i => Console.WriteLine("Aggregate OnNext({0})", i),

() => Console.WriteLine("Aggregate OnCompleted()"));

// 値の発行~完了通知

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(10)");

s.OnNext(10);

Console.WriteLine("OnNext(100)");

s.OnNext(100);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

初期値に 5を渡している点が 1つ前のサンプルコードと異なります。実行結果を下記に示します。

OnNext(1)

log(5, 1)

OnNext(10)

log(6, 10)

Page 97: Reactive extensions入門v0.1

90

OnNext(100)

log(16, 100)

OnCompleted()

Aggregate OnNext(116)

Aggregate OnCompleted()

最初の OnNextからデリゲートが呼ばれていることが確認できます。

因みに、この Aggregateメソッドを使うと数値の合計といった集計だけではなく、値の収集も可

能です。コード例は下記のようになります。

var s = new Subject<int>();

// IObservable<T>のシーケンスから渡される値を集める

// 初期値が空のリスト

s.Aggregate(new List<int>(), (list, value) =>

{

// 通知された値をリストの保持しておく

list.Add(value);

return list;

})

// 購読。リストの内容を表示

.Subscribe(list =>

{

foreach (var i in list)

{

Console.WriteLine("value : {0}", i);

}

},

() => Console.WriteLine("Aggregate OnCompleted()"));

// 値の発行~完了通知

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(10)");

s.OnNext(10);

Console.WriteLine("OnNext(100)");

s.OnNext(100);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

Page 98: Reactive extensions入門v0.1

91

実行結果を下記に示します。

OnNext(1)

OnNext(10)

OnNext(100)

OnCompleted()

value : 1

value : 10

value : 100

Aggregate OnCompleted()

Aggregateメソッドで ToListメソッドと同様の処理ができていることが確認できます。

4.6.11. Scan メソッド

次は、Scanメソッドについて説明します。Scanメソッドは Aggregateメソッドと同じシグネチ

ャを持ちます。違いは、Aggregateが集計結果しか後続に流さないのに対して Scanメソッドは

集計経過を後続に流します。Aggregateと同じコードを Scanメソッドを使って書き換えたコー

ド例を下記に示します。

var s = new Subject<int>();

// Aggregateで合計値を求めるのと同じ処理を Scanメソッドで行う。

s.Scan((x, y) =>

{

Console.WriteLine("log({0}, {1})", x, y);

return x + y;

})

// 購読

.Subscribe(

i => Console.WriteLine("Scan OnNext({0})", i),

() => Console.WriteLine("Scan OnCompleted()"));

// 値の発行~完了通知

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(10)");

s.OnNext(10);

Console.WriteLine("OnNext(100)");

s.OnNext(100);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

Page 99: Reactive extensions入門v0.1

92

実行結果を下記に示します。

OnNext(1)

Scan OnNext(1)

OnNext(10)

log(1, 10)

Scan OnNext(11)

OnNext(100)

log(11, 100)

Scan OnNext(111)

OnCompleted()

Scan OnCompleted()

OnNextが呼び出される度に、Subscribeで購読しているところに値が流れていることが確認でき

ます。これが Scanメソッドの挙動になります。Aggregateと同様に初期値を渡すオーバーロー

ドもあります。使用例を下記に示します。

var s = new Subject<int>();

// 初期値 5で Scanを使い合計を求める

s.Scan(5, (x, y) =>

{

Console.WriteLine("log({0}, {1})", x, y);

return x + y;

})

// 購読

.Subscribe(

i => Console.WriteLine("Scan OnNext({0})", i),

() => Console.WriteLine("Scan OnCompleted()"));

// 値の発行~完了通知

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(10)");

s.OnNext(10);

Console.WriteLine("OnNext(100)");

s.OnNext(100);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

実行結果を下記に示します。

OnNext(1)

Page 100: Reactive extensions入門v0.1

93

log(5, 1)

Scan OnNext(6)

OnNext(10)

log(6, 10)

Scan OnNext(16)

OnNext(100)

log(16, 100)

Scan OnNext(116)

OnCompleted()

Scan OnCompleted()

実行結果から、最初の OnNextで値が発行されたタミングで Scanメソッドに渡したデリゲート

が実行されて、結果が Subscribeで購読しているところにながれていることが確認できます。

4.6.12. GroupBy メソッド

次に、GroupByメソッドについて説明します。これは、SQL文の GROUP BYのように特定の値

でグルーピングしてくれる機能を提供します。メソッドのシグネチャとしては、IObservable<T>

の Tからグルーピングのキーとなる値を抽出するデリゲートを渡します。戻り値は、

IObservable<IGroupedObservable<TKey, T>>になります。少し、

IGroupedObservable<TKey, T>について説明します。

IGroupedObservable<TKey, T>は、IObservable<T>を拡張したンターフェースで TKey型

の Keyプロパテゖを追加しています。この Keyプロパテゖで、何でグルーピングされたかを表す

以外は通常の IObservable<T>と変わりはありません。コード例を下記に示します。

var s = new Subject<int>();

// 10で割った余りでグルーピングする。(つまり1の位の数字でグルーピング)

s.GroupBy(i => i % 10)

// IGroupedObservable<int, int>が Subscribeの OnNextに渡ってくる

.Subscribe(

g =>

{

// GroupByの OnNextの処理

Console.WriteLine("集計開始 {0}", g.Key);

// 値を集計 配列にまとめて

g.ToArray()

// ,区切りの文字列にして

.Select(array => string.Join(", ", array))

// 結果を表示

.Subscribe(

values => Console.WriteLine("集計結果 Key: {0}, Values: {{{1}}}", g.Key, values),

Page 101: Reactive extensions入門v0.1

94

() => Console.WriteLine("集計終了 Key: {0}", g.Key));

},

ex =>

{

// エラー処理(今回の例ではエラーは起きないけど・・・

Console.WriteLine("GroupBy OnError {0}", ex.Message);

},

() =>

{

// GroupByの結果が OnCompletedになった時の処理

Console.WriteLine("GroupBy OnCompleted");

});

// 値をいくつか発行して終了

Console.WriteLine("OnNext(13)");

s.OnNext(13);

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(11)");

s.OnNext(11);

Console.WriteLine("OnNext(42)");

s.OnNext(42);

Console.WriteLine("OnNext(21)");

s.OnNext(21);

Console.WriteLine("OnNext(12)");

s.OnNext(12);

Console.WriteLine("OnNext(23)");

s.OnNext(23);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

多少複雑ですが、コードを理解するポントは GroupByの結果が

IObservable<IGroupedObservable<int, int>>になっている点です。これを Subscribeすると

OnNextには IGroupedObservable<int, int>が渡ってきます。IGroupedObservableは、Key

プロパテゖがある以外は IObservableと同じなので、これまで使ってきた拡張メソッドがすべて

使えます。ここの例では、発行された値をすべて”, ”区切りの文字列にして出力しています。実行

結果を下記に示します。

OnNext(13)

Page 102: Reactive extensions入門v0.1

95

集計開始 3

OnNext(1)

集計開始 1

OnNext(11)

OnNext(42)

集計開始 2

OnNext(21)

OnNext(12)

OnNext(23)

OnCompleted()

集計結果 Key: 3, Values: {13, 23}

集計終了 Key: 3

集計結果 Key: 1, Values: {1, 11, 21}

集計終了 Key: 1

集計結果 Key: 2, Values: {42, 12}

集計終了 Key: 2

GroupBy OnCompleted

新しいグループに属する値が発行される度に、“集計開始“という文字列が出力されます。これは

GroupByをした結果の OnNext内で出力しているものです。新しいグループに属する値が出てく

ると即座に IGroupedObservableが作成されて Subscribeしている箇所に流れてくることがわか

ります。そして、値を発行しおわったあとに、“集計結果”という文字列が出力されます。これは、

もとになる IObservableのシーケンスが完了したため、IGroupedObservableの ToArrayが値を

まとめた結果を後続の処理に流し文字列として加工して出力しているものになります。最後に、

IGroupedObservableの OnCompletedの”集計終了”という文字列が出力されます。そして、最

後に GroupByの結果を購読しているところの OnCompletedが呼ばれて”GroupBy

OnCompleted”と表示されています。

このコードは Subscribeしている中で、さらに Subscribeしていたり ToArrayなどの他のメソッ

ドの挙動もかかわってくるため若干複雑になっています。処理の流れがメージできない場合は、

コードを実際に書いてみてデバッガで止めて処理の流れを追うなどして動作を確認してみましょ

う。

4.6.13. GroupByUntil メソッド

ここでは、GroupByUntilメソッドについて説明します。これは「4.3.4. SkipUntilと TakeUntil

メソッド」で説明したメソッドと同じような動きをします。SkipUntilは引数で渡した

IObservable<T>の OnNextが発行されるまで値をスキップします。TakeUntilは引数で渡した

IObservable<T>の OnNextが発行されるまで値を後ろに流したりします。このように

GroupByUntilメソッドでも、IObservable<T>を使ってメソッドの処理の開始や終了のタミン

グを制御することが出来ます。

動きの詳しい説明の前に GroupByUntilメソッドのシグネチャを下記に示します。

Page 103: Reactive extensions入門v0.1

96

public static IObservable<IGroupedObservable<TKey, TSource>> GroupByUntil<TSource, TKey,

TDuration>(

this IObservable<TSource> source,

Func<TSource, TKey> keySelector,

Func<IGroupedObservable<TKey, TSource>, IObservable<TDuration>> durationSelector

)

第三引数にFunc<IGroupedObservable<TKey, TSource>, IObservable<TDuration>>という

型のデリゲートを受け取っているところが、理解に苦しむ点だと思います。これは、第二引数の

keySelectorで選択された値で新しいグループが出来たときに呼び出されるデリゲートになりま

す。そのため引数がグループを表す IGroupedObservable<TKey, TSource>になります。そして、

戻り値で、このグループの集計を終了するきっかけを OnNextで通知する

IObservable<TDuration>を返します。コード例を下記に示します。

// 値をランダムで出したいので

var r = new Random();

// グループピングの終了を通知するための IObservable

var duration = new Subject<Unit>();

// GroupByUntilの使用例

var subscriber = Observable.Interval(TimeSpan.FromMilliseconds(500))

// 500ミリ秒間隔で乱数発生

.Select(_ => r.Next(1000))

.GroupByUntil(

// 1の位の数でグルーピング

l => l % 10,

// durationで OnNextが発行されるまでの間グルーピングをする。

// 全てのグループに対して、同じ IObservableを返しているので、同じタミングで

// グルーピングが終わる。

l => duration.AsObservable())

// 購読

.Subscribe(

g =>

{

// GroupByの OnNextの処理

Console.WriteLine("集計開始 {0}", g.Key);

// 値を集計 配列にまとめて

g.ToArray()

// ,区切りの文字列にして

.Select(array => string.Join(", ", array))

Page 104: Reactive extensions入門v0.1

97

// 結果を表示

.Subscribe(

values => Console.WriteLine("集計結果 Key: {0}, Values: {{{1}}}", g.Key, values),

() => Console.WriteLine("集計終了 Key: {0}", g.Key));

},

ex =>

{

// エラー処理(今回の例ではエラーは起きないけど・・・

Console.WriteLine("GroupBy OnError {0}", ex.Message);

},

() =>

{

// GroupByの結果が OnCompletedになった時の処理

Console.WriteLine("GroupBy OnCompleted");

});

while(true)

{

// 待ち

Console.WriteLine("Enterを押すまで集計します。(終了したい場合は endと入力してください)");

if (Console.ReadLine() == "end")

{

break;

}

// durationの OnNextでいったんグルーピングの集計が一区切り

duration.OnNext(Unit.Default);

}

// 後始末

subscriber.Dispose();

このコードでは 0.5秒(500ms)間隔でランダムに 0~999の値を発行しています。そして、発行

された値の 1の位でグルーピングをしています。GroupByUntilでは、グルーピングの終了のタ

ミングを通知する IObservableを返すデリゲートで全て同じ IObservableを返しています。その

ため、全てのグルーピングの終了のタミングが同じになります。グルーピングの終了のタミン

グは、コンソールで Enterを押したタミングになります。Enterを押すと、今までのグルーピン

グの結果を表示して、新たにグルーピングを行います。endと入力して Enterを押すと処理が終

了します。実行結果を下記に示します。

Enterを押すまで集計します。(終了したい場合は endと入力してください)

Page 105: Reactive extensions入門v0.1

98

集計開始 8

集計開始 4

集計開始 1

集計開始 6

集計開始 9

集計開始 7

集計開始 0

集計開始 5

集計開始 2

集計開始 3

集計結果 Key: 8, Values: {358, 808, 48, 308}

集計終了 Key: 8

集計結果 Key: 4, Values: {724, 544, 594, 604}

集計終了 Key: 4

集計結果 Key: 1, Values: {371, 161, 81, 141, 221}

集計終了 Key: 1

集計結果 Key: 6, Values: {426, 666, 456}

集計終了 Key: 6

集計結果 Key: 9, Values: {309, 379, 689, 19}

集計終了 Key: 9

集計結果 Key: 7, Values: {967, 57, 557, 477}

集計終了 Key: 7

集計結果 Key: 0, Values: {650, 330, 590, 570, 780, 550}

集計終了 Key: 0

集計結果 Key: 5, Values: {545}

集計終了 Key: 5

集計結果 Key: 2, Values: {462, 532, 492, 902, 252, 542, 692, 852}

集計終了 Key: 2

集計結果 Key: 3, Values: {663, 433, 763}

集計終了 Key: 3

Enterを押すまで集計します。(終了したい場合は endと入力してください)

集計開始 2

集計開始 7

集計開始 8

集計開始 0

集計開始 4

Page 106: Reactive extensions入門v0.1

99

集計開始 9

end

集計結果 Key: 2, Values: {112}

集計終了 Key: 2

集計結果 Key: 7, Values: {357}

集計終了 Key: 7

集計結果 Key: 8, Values: {598}

集計終了 Key: 8

集計結果 Key: 0, Values: {850}

集計終了 Key: 0

集計結果 Key: 4, Values: {644}

集計終了 Key: 4

集計結果 Key: 9, Values: {699}

集計終了 Key: 9

長い実行結果ですが、実行後暫く放置して Enterを押して、その後 endと入力して Enterを押し

た実行結果になります。一度 Enterを押してグルーピングが終了したあとにも、新たにグルーピ

ングが行われています。これはもとになる Observable.Intervalが終了していないためです。グ

ルーピングが一旦終了したあとに、新たに値が発行されるため再度グルーピングが行われます。こ

のように、GroupByでは素直に書けない一定間隔でのグルーピングなどが GroupByUntilでは行

えます。このように、GroupByでは素直に書けない一定間隔でのグルーピングなどが

GroupByUntilでは行えます。また、今回の例では全ての IGroupedObservableに対して同じ

IObservableを返していたため、全てのグルーピングの終了タミングが同じになっていました

が、IGroupedObservableごとに異なる IObservableを返すことで、グルーピングの終了タミ

ングをグループごとに指定することも出来ます。

4.7. IObservable<T>から IEnumerable<T>への変換メソッド

ここでは、IObservable<T>から IEnumerable<T>への変換メソッドについて説明します。既存

の IEnumerable<T>を受け取るメソッドとの連携に使えるかもしれませんが、使いどころは書い

ている私もよくわかりません。

4.7.1. ToEnumerable メソッド

このメソッドは、単純に IObservable<T>から IEnumerable<T>へ変換します。

ToEnumerable<T>で IEnumerable<T>に変換した後に発行された値が IEnumerable<T>に流

れて、OnCompletedで IEnumerable<T>のシーケンスも終了します。コード例を下記に示しま

す。

var s = new Subject<int>();

// 進捗状況を管理するためのWaitHandle

var gate = new EventWaitHandle(false, EventResetMode.AutoReset);

Observable.Start(() =>

Page 107: Reactive extensions入門v0.1

100

{

// IO<T> -> IE<T>への変換

var e = s.ToEnumerable();

// 待ち解除

gate.Set();

// 値の表示

foreach (var i in e)

{

Console.WriteLine("value : {0}", i);

}

})

// 最後にWaitHandleを発火

.Finally(() => gate.Set())

.Subscribe();

// ToEnumerableされるまで待つ

gate.WaitOne();

// 値の発行から Completed

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(2)");

s.OnNext(2);

Console.WriteLine("OnNext(3)");

s.OnNext(3);

Console.WriteLine("OnCompleted()");

s.OnCompleted();

// 列挙が終わるのを待つ

gate.WaitOne();

実行結果を下記に示します。

OnNext(1)

OnNext(2)

OnNext(3)

OnCompleted()

value : 1

value : 2

Page 108: Reactive extensions入門v0.1

101

value : 3

今回のプログラムは複数のスレッドから値の発行と列挙を行っているため、毎回同じ実行結果にな

るとは限りません出力が前後することがあります。複数スレッドに分けたのは ToEnumerableの

結果を IEnumerable<T>でループを行うと、値が発行されるまでブロックするという挙動のため

です。因みに、この条件は Hotな IObservable<T>を使用している場合に起きる問題で Coldな

IObservable<T>を使用している場合は問題になることは、ありません。コード例を下記に示し

ます。

// Coldな IObservable<int>を作成

var s = Observable.Range(1, 5);

// IEnumerable<int>に変換して列挙

foreach (var i in s.ToEnumerable())

{

Console.WriteLine("value : {0}", i);

}

実行結果を下記に示します。

value : 1

value : 2

value : 3

value : 4

value : 5

普通に値の列挙が行われていることが確認できます。

4.7.2. Latest メソッド

Latestメソッドについて説明します。Latestメソッドは IObservable<T>から IEnumerable<T>

へ変換を行うメソッドです。変換後の IEnumerable<T>では MoveNextメソッドの呼び出し時に、

変換元の IObservable<T>からの値の発行を待ちます。既に値が発行されていたり、値が発行さ

れると Trueを返し Currentプロパテゖで、発行された値の取得ができるようになります。コード

例を下記に示します。

// 値の発行元

var s = new Subject<int>();

// Latestで取得した IEnumerable<int>の値を印字

Observable.Start(() =>

{

Console.WriteLine("Start latest loop");

foreach (var i in s.Latest())

{

Console.WriteLine("LatestValue : {0}", i);

Page 109: Reactive extensions入門v0.1

102

}

Console.WriteLine("End latest loop");

});

// 1秒間隔で値 1~10の値を発行

Observable.Start(() =>

{

foreach (var i in Enumerable.Range(1, 10))

{

Thread.Sleep(1000);

Console.WriteLine("OnNext({0})", i);

s.OnNext(i);

}

Console.WriteLine("OnCompleted()");

s.OnCompleted();

});

// 終了しないため待つ

Console.ReadLine();

実行結果を下記に示します。

Start latest loop

OnNext(1)

LatestValue : 1

OnNext(2)

LatestValue : 2

OnNext(3)

LatestValue : 3

OnNext(4)

LatestValue : 4

OnNext(5)

LatestValue : 5

OnNext(6)

LatestValue : 6

OnNext(7)

LatestValue : 7

OnNext(8)

LatestValue : 8

Page 110: Reactive extensions入門v0.1

103

OnNext(9)

LatestValue : 9

OnNext(10)

OnCompleted()

LatestValue : 10

End latest loop

動作結果から、OnNextで値が発行された後に、Latestで取得した IEnumerable<T>のループで

値が取得できていることが確認できます。また、もとになる IObservable<T>のシーケンスが終

了したタミングで Latestメソッドで変換した結果の IEnumerable<T>のループも抜けている

ことが確認できます。

今回の例では、値の発行と Latestで変換した IEnumerable<T>から取得できる値が 1対 1の関

係にあるように見えますが、Latestで変換した IEnumerable<T>が返す値はあくまで最後に発行

された値になります。例えば、IEnumerable<T>でのループが 1周する間に 2回値が発行された

場合、IEnumerable<T>は最初に発行された値は無視して 2回目に発行された値を返します。こ

の挙動を確認するコードを下記に示します。

// 値の発行元

var s = new Subject<int>();

// Latestで変換した IEnumerableから IEnumeratorを取得

var e = s.Latest().GetEnumerator();

// 1を発行して IEnumeratorから値を取得

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("MoveNext : {0}", e.MoveNext());

Console.WriteLine("Current : {0}", e.Current);

// 10と 100を発行して IEnumeratorから値を取得

Console.WriteLine("OnNext(10)");

s.OnNext(10);

Console.WriteLine("OnNext(100)");

s.OnNext(100);

Console.WriteLine("MoveNext : {0}", e.MoveNext());

Console.WriteLine("Current : {0}", e.Current);

// IObservable<T>のシーケンスを終了した MoveNextを呼んでみる

Console.WriteLine("OnCompleted()");

s.OnCompleted();

Page 111: Reactive extensions入門v0.1

104

Console.WriteLine("MoveNext : {0}", e.MoveNext());

実行結果を下記に示します。

OnNext(1)

MoveNext : True

Current : 1

OnNext(10) ← ※1

OnNext(100) ← ※2

MoveNext : True

Current : 100 ← ※3

OnCompleted()

MoveNext : False

実行結果の※1と※2で 10と 100の値を 2つ発行していますが、※3で取得している値は最後

に発行された 100の値を取得しています。以上が Latestメソッドの挙動になります。

4.7.3. MostRecent メソッド

Latestメソッドと同じような動きをするメソッドに MostRecentというメソッドがあります。こ

のメソッドも IObservable<T>から IEnumerable<T>へ変換を行いますが、Latestメソッドが

最後に発行された値を返して、値が無い場合は値が発行されるまで待つのに対して、MostRecent

は最後に発行された値をキャッシュしておいて、キャッシュを返します。そのため、

IEnumerable<T>から値を取得する際にブロックされることがありません。コード例を下記に示

します。

// 値の発行元

var s = new Subject<int>();

// 初期値を-1にして IObservable<T>から IEnumerableに変換して IEnumeratorを取得

var e = s.MostRecent(-1).GetEnumerator();

// 一度も値を発行してない状態

Console.WriteLine("一度も値を発行していない場合は初期値が返される");

Console.WriteLine("MoveNext : {0}", e.MoveNext());

Console.WriteLine("CurrentValue : {0}", e.Current);

Console.WriteLine("MoveNext : {0}", e.MoveNext());

Console.WriteLine("CurrentValue : {0}", e.Current);

// 値を発行した状態の確認

Console.WriteLine("-----");

Console.WriteLine("OnNext(10)");

s.OnNext(10);

Page 112: Reactive extensions入門v0.1

105

Console.WriteLine("最後に発行した値が返されるようになる");

Console.WriteLine("MoveNext : {0}", e.MoveNext());

Console.WriteLine("CurrentValue : {0}", e.Current);

Console.WriteLine("MoveNext : {0}", e.MoveNext());

Console.WriteLine("CurrentValue : {0}", e.Current);

// IEnumeratorで値を取得する前に 2回値が発行された状態の確認

Console.WriteLine("-----");

Console.WriteLine("OnNext(100)");

s.OnNext(100);

Console.WriteLine("OnNext(1000)");

s.OnNext(1000);

Console.WriteLine("最後に発行した値が返されるようになる");

Console.WriteLine("MoveNext : {0}", e.MoveNext());

Console.WriteLine("CurrentValue : {0}", e.Current);

Console.WriteLine("MoveNext : {0}", e.MoveNext());

Console.WriteLine("CurrentValue : {0}", e.Current);

// OnCompletedの後の状態の確認

Console.WriteLine("-----");

Console.WriteLine("OnCompleted()");

s.OnCompleted();

Console.WriteLine("OnCompletedを呼ぶと、MoveNextが Falseを返すようになる");

Console.WriteLine("MoveNext : {0}", e.MoveNext());

このコードの実行結果を下記に示します。

一度も値を発行していない場合は初期値が返される

MoveNext : True

CurrentValue : -1

MoveNext : True

CurrentValue : -1

-----

OnNext(10)

最後に発行した値が返されるようになる

MoveNext : True

CurrentValue : 10

MoveNext : True

CurrentValue : 10

Page 113: Reactive extensions入門v0.1

106

-----

OnNext(100)

OnNext(1000)

最後に発行した値が返されるようになる

MoveNext : True

CurrentValue : 1000

MoveNext : True

CurrentValue : 1000

-----

OnCompleted()

OnCompletedを呼ぶと、MoveNextが Falseを返すようになる

MoveNext : False

このように LatestではMoveNextでブロックされるようなケースでもMostRecentではブロック

されずに最後の値のキャッシュを返すことが確認できます。

4.7.4. Nextメソッド

次に、LatestメソッドとMostRecentメソッドと同様に IObservable<T>から IEnumerable<T>

へ変換する Nextメソッドについて説明します。Nextメソッドは Latestメソッドと同じように

MoveNextで値が発行されるのを待機する IEnumerable<T>を返します。Latestメソッドが返す

IEnumerable<T>との違いは、Latestは最後の値を 1回だけキャッシュしますが Nextメソッド

が返す IEnumerable<T>は、キャッシュを行いません。この挙動の違いを示すプログラムを下記

に示します。

// 初期値 -1の BehaviorSubject

var s = new BehaviorSubject<int>(-1);

// Latestの動作

Observable.Start(() =>

{

Console.WriteLine("Latest start");

foreach (var i in s.Latest())

{

Console.WriteLine("Latest : {0}", i);

}

});

// Nextの動作

Observable.Start(() =>

{

Page 114: Reactive extensions入門v0.1

107

Console.WriteLine("Next start");

foreach (var i in s.Next())

{

Console.WriteLine("Next : {0}", i);

}

});

// 1秒間隔で値を発行する

Observable.Start(() =>

{

var i = 0;

while (true)

{

Thread.Sleep(1000);

Console.WriteLine("OnNext({0})", ++i);

s.OnNext(i);

}

});

// 待機

Console.WriteLine("Please any key.");

Console.ReadLine();

Console.WriteLine("End NextSample");

BehaviorSubjectは初期値を持った Subjectです。BehaviorSubject<T>クラスの詳細について

は、5.2を参照してください。上記プログラムの実行結果を下記に示します。

Please any key.

Latest start

Next start

Latest : -1 <- ※1 Latestは最後の値をキャッシュしているので BehaviorSubjectの初期値-1を表示する

OnNext(1)

Next : 1 <- ※2 Nextは最後の値をキャッシュしないので 1から表示する

Latest : 1

OnNext(2)

Next : 2

Latest : 2

OnNext(3)

Next : 3

Page 115: Reactive extensions入門v0.1

108

Latest : 3

OnNext(4)

Latest : 4

Next : 4

End NextSample

※1の箇所にある通り Latestでは最後の値をキャッシュするためBehaviorSubjectの初期値であ

る-1を表示しています。それに対して Nextメソッドでは、初期値の-1は表示せずに 1からの表

示になっていることが確認できます。

4.8. ToEvent メソッド

ToEventメソッドは、IObservable<T>から IEventSource<T>という型に変換を行います。こ

の IEventSource<T>という型は OnNextベントを持つ型で、IObservable<T>から値が発行

される度に OnNextベントを発火させます。コード例を下記に示します。

var s = new Subject<int>();

// IO<T>から OnNextベントを発行する IEventSourct<T>へ変換

var evt = s.ToEvent();

// OnNextをベントで受け取れる

evt.OnNext += i =>

{

Console.WriteLine("value : {0}", i);

};

// 値の発行

Console.WriteLine("OnNext(1)");

s.OnNext(1);

Console.WriteLine("OnNext(2)");

s.OnNext(2);

Console.WriteLine("OnNext(3)");

s.OnNext(3);

実行結果を下記に示します。

OnNext(1)

value : 1

OnNext(2)

value : 2

OnNext(3)

value : 3

Page 116: Reactive extensions入門v0.1

109

Subjectで OnNextが実行される度に OnNextベントが発行されていることが確認できます。

4.9. 重複を排除するメソッド

ここでは、IObservable<T>のシーケンスから重複要素を排除するメソッドについて紹介します。

4.9.1. Distinct メソッド

Distinctメソッドは、IObservable<T>のシーケンスから、一度通過した値を二度と通さないメソ

ッドです。コード例を下記に示します。

// Distinctで重複を排除して購読

s.Distinct()

.Subscribe(

// 値を出力

i => Console.WriteLine("OnNext({0})", i),

// OnCompletedしたことを出力

() => Console.WriteLine("OnCompleted()"));

// 1~3の値を発行

Console.WriteLine("OnNext 1~3");

s.OnNext(1);

s.OnNext(2);

s.OnNext(3);

// 繰り返し 1~3の値を発行

Console.WriteLine("OnNext 1~3");

s.OnNext(1);

s.OnNext(2);

s.OnNext(3);

// 2~4の値を発行

Console.WriteLine("OnNext 2~4");

s.OnNext(2);

s.OnNext(3);

s.OnNext(4);

Console.WriteLine("OnCompleted call.");

s.OnCompleted();

Page 117: Reactive extensions入門v0.1

110

このコードでは、順番に 1, 2, 3, 1, 2, 3, 2, 3, 4という値を発行する IObservable<int>のシー

ケンスに対して Distinctを適用して Subscribeしています。実行結果を下記に示します。

OnNext 1~3

OnNext(1)

OnNext(2)

OnNext(3)

OnNext 1~3

OnNext 2~4

OnNext(4)

OnCompleted call.

OnCompleted()

実行結果からわかるように、2回目の 1, 2, 3の値を発行している所では、Subscribeの OnNext

が呼ばれていません。これは Distinctメソッドで一度出現した値をブロックしているためです。

次の 2~4の値を発行している箇所では、既に発行されている 2と 3はブロックされていますが、

4は Subscribeの OnNextに渡っていることが確認できます。

4.9.2. Distinct メソッドのオーバーロード

Distinctメソッドには、Func<T, TKey>という Tから重複の判定を行う値を選択するためのデリ

ゲートを渡すオーバーロードと、IEqualityComparer<T>ンターフェースを実装したクラスで、

値が等しいかどうかの判定方法を指定するオーバーロードと、Func<T, TKey>型のデリゲートと

IEqualityComparer<T>ンターフェースを実装したクラスの両方を指定するオーバーロードが

あります。各オーバーロードのシグネチャを下記に示します。

// 比較方法を指定するオーバーロード

public static IObservable<T> Distinct<T>(

this IObservable<T> source,

IEqualityComparer<T> comparer

)

// 比較する値を選択するデリゲートを指定するオーバーロード

public static IObservable<T> Distinct<T, TKey>(

this IObservable<T> source,

Func<T, TKey> keySelector

)

// 比較する値を選択するデリゲートと、値の比較方法を指定するオーバーロード

public static IObservable<T> Distinct<T, TKey>(

this IObservable<T> source,

Func<T, TKey> keySelector,

Page 118: Reactive extensions入門v0.1

111

IEqualityComparer<TKey> comparer

)

この各種オーバーロードを使うことで、柔軟に重複値のフゖルタリングを行えます。ここでは、最

後の Func<T, TKey>型のデリゲートと IEqualityComparer<T>ンターフェースの実装クラス

の両方を指定するオーバーロードのコード例を示します。Func<T, TKey>型のデリゲートのみを

指定するオーバーロードと、IEqualityComparer<T>ンターフェースの実装クラスのみを指定

するオーバーロードのサンプルコードについては、単純に引数が少ないだけなので、ここでは割愛

します。

まず、サンプルコードで使用するクラスの定義を下記に示します。ここでは、名前と年齢をもった

人を表すクラスと、int型を 1の位を除いた状態で比較するクラスの2つのコードを示します。

/// <summary>

/// 人

/// </summary>

class Person

{

public int Age { get; set; }

public string Name { get; set; }

public override string ToString()

{

return string.Format("{0}: {1}歳", this.Name, this.Age);

}

}

/// <summary>

/// 1の位を省いた状態で比較を行う。

/// </summary>

public class GenerationEqualityComparer : IEqualityComparer<int>

{

/// <summary>

/// 1の位を除いた数が等しければ trueを返す

/// </summary>

public bool Equals(int x, int y)

{

return (x / 10) == (y / 10);

}

public int GetHashCode(int obj)

Page 119: Reactive extensions入門v0.1

112

{

return (obj / 10).GetHashCode();

}

}

上記の 2つのクラスを使った、Distinctメソッドの使用例のコードを下記に示します。

var s = new Subject<Person>();

s.Distinct(

// Personクラスの Ageプロパテゖの値で比較する

p => p.Age,

// 比較方法は GenerationEqualityComparerを使用する

new GenerationEqualityComparer())

// 購読

.Subscribe(

// 値を出力

p => Console.WriteLine(p),

// OnCompletedしたことを出力

() => Console.WriteLine("OnCompleted()"));

// 10代, 20代, 30代の人を発行する

Console.WriteLine("OnNext 10代~30代");

s.OnNext(new Person { Name = "田中 一郎", Age = 15 });

s.OnNext(new Person { Name = "田中 二郎", Age = 22 });

s.OnNext(new Person { Name = "田中 三郎", Age = 38 });

// 別の名前, 年齢の 10代, 20代, 30代の人を発行する

Console.WriteLine("OnNext 10代~30代");

s.OnNext(new Person { Name = "木村 一郎", Age = 12 });

s.OnNext(new Person { Name = "木村 二郎", Age = 28 });

s.OnNext(new Person { Name = "木村 三郎", Age = 31 });

// 40代の人を発行する

Console.WriteLine("OnNext 40代");

s.OnNext(new Person { Name = "井上 エリザベス", Age = 49 });

Console.WriteLine("OnCompleted call.");

s.OnCompleted();

Page 120: Reactive extensions入門v0.1

113

上記のコードの実行結果を下記に示します。

OnNext 10代~30代

田中 一郎: 15歳

田中 二郎: 22歳

田中 三郎: 38歳

OnNext 10代~30代

OnNext 40代

井上 エリザベス: 49歳

OnCompleted call.

OnCompleted()

上記の結果からわかるとおり、Personクラスの Ageプロパテゖの 10の位の値で重複チェックが

行われていることが確認できます。

4.9.3. DistinctUntilChanged メソッド

DistinctUntilChangedメソッドは、直前の値と異なる値が出現するまで値をブロックするメソッ

ドになります。Distinctメソッドが、今まで発行された値すべてに対して重複チェックを行ってい

たのに対してDistinctUntilChangedメソッドは直前の値のみをチェック対象にする点が異なりま

す。コード例を下記に示します。

var s = new Subject<int>();

// Distinctで重複を排除して購読

s.DistinctUntilChanged()

.Subscribe(

// 値を出力

i => Console.WriteLine("OnNext({0})", i),

// OnCompletedしたことを出力

() => Console.WriteLine("OnCompleted()"));

// 1~3の値を 2回ずつ発行

Console.WriteLine("OnNext 1 -> 1 -> 2 -> 2 -> 3 -> 3");

s.OnNext(1);

s.OnNext(1);

s.OnNext(2);

s.OnNext(2);

s.OnNext(3);

s.OnNext(3);

// 1~3の値を発行

Page 121: Reactive extensions入門v0.1

114

Console.WriteLine("OnNext 1~3");

s.OnNext(1);

s.OnNext(2);

s.OnNext(3);

Console.WriteLine("OnCompleted call.");

s.OnCompleted();

実行結果を下記に示します。

OnNext 1 -> 1 -> 2 -> 2 -> 3 -> 3

OnNext(1)

OnNext(2)

OnNext(3)

OnNext 1~3

OnNext(1)

OnNext(2)

OnNext(3)

OnCompleted call.

OnCompleted()

最初に 1, 1, 2, 2, 3, 3という値を発行している箇所では、Subscribeの OnNextに 1, 2, 3とい

う値しか渡っていないことから、値の変化が無い場合にブロックされていることが確認できます。

また、そのあとに 1, 2, 3の値を発行している箇所で Subscribeの OnNextに 1, 2, 3の値が渡っ

ていることから、Distinctメソッドとの動作の違いである、直前の値しか比較の対象にしないとい

う点が確認できます。

DistinctUntilChangedメソッドにも、比較対象の値を選択する Func<T, TKey>型のデリゲート

を受け取るオーバーロードと、IEqualityComparer<T>ンターフェースを実装したクラスを受

け取るオーバーロードがありますが、Distinctメソッドと基本的に同じためサンプルコードは割愛

します。

4.10. Buffer メソッドと Window メソッド

ここでは、BufferメソッドとWindowメソッドについて説明します。まず、最初に Bufferメソッ

ドについて説明して、次にWindowメソッドについて説明します。Bufferメソッドは、もとにな

る IObservable<T>のシーケンスから指定した数、時間、タミングで値をまとめて

IObservable<IList<T>>のシーケンスとして後続に流すメソッドになります。

4.10.1. 数でまとめる Bufferメソッドのオーバーロード

まず、最初に一番直感的に使える指定した数で値をまとめるオーバーロードについて説明します。

メソッドのシグネチャを以下に示します。

Page 122: Reactive extensions入門v0.1

115

public static IObservable<IList<TSource>> Buffer<TSource>(

this IObservable<TSource> source,

int count)

countで、値をまとめる個数を指定します。このメソッドの使用例を書きに示します。

// 1~10の値を発行する IObservable<int>のシーケンス

Observable.Range(1, 10)

// 3つずつの値に分ける

.Buffer(3)

.Subscribe(

l =>

{

// IList<int>の内容を出力

Console.WriteLine("-- Buffer start");

foreach (var i in l)

{

Console.WriteLine(i);

}

},

// 完了

() => Console.WriteLine("OnCompleted"));

Observable.Rangeを使って 1~10の 10個の値を発行する IObservable<int>のシーケンスを

作成して Buffer(3)で 3つずつ値をまとめています。順当にいくと、1つ値が余ってしまいます。

その値がどのように扱われるのかに注意して下記実行結果を確認してください。

-- Buffer start

1

2

3

-- Buffer start

4

5

6

-- Buffer start

7

8

9

-- Buffer start

10

Page 123: Reactive extensions入門v0.1

116

OnCompleted

結果からわかるように、(1,2,3), (4,5,6), (7,8,9), (10)のようにまとめられています。最後のあ

まった数は、無視されるのではなく、残ったものだけを IList<int>にまとめて、後続に流します。

数を指定する Bufferメソッドのオーバーロードには、下記のシグネチャのように 2つの int型の

数を受け取るものがあります。

public static IObservable<IList<TSource>> Buffer<TSource>(

this IObservable<TSource> source,

int count,

int skip)

引数の countは、値をまとめる数で skipは、Bufferでまとめ終わった後に次の値を収集するスタ

ート地点を何処にするのか指定する引数です。skipで指定した数だけ、発行される値を無視した

あとに count個の値を集め始めます。最初に使用した引数が1つの Bufferメソッドは、count個

ずつ値をまとめたあとの、次に値をまとめ始める起点が count個の値を飛ばした個所になるため、

countと skipに同じ値を指定した場合と等しくなります。

このオーバーロードのコード例を下記に示します。

// 1~10の値を発行する IObservable<int>のシーケンス

Observable.Range(1, 10)

// 3つずつの値に分けて、値は 2つ飛ばし

.Buffer(3, 2)

.Subscribe(

l =>

{

// IList<int>の内容を出力

Console.WriteLine("-- Buffer start");

foreach (var i in l)

{

Console.WriteLine(i);

}

},

// 完了

() => Console.WriteLine("OnCompleted"));

Observable.Rangeメソッドを使用して 1~10の 10個の値を発行する IObservable<int>のシ

ーケンスを作成して、Buffer(3, 2)の呼び出しで 3こずつ、値は 2つ飛ばしでまとめています。実

行結果を下記に示します。

-- Buffer start

1

2

3

Page 124: Reactive extensions入門v0.1

117

-- Buffer start

3

4

5

-- Buffer start

5

6

7

-- Buffer start

7

8

9

-- Buffer start

9

10

OnCompleted

実行結果からわかるように(1,2,3), (3,4,5), (5,6,7), (7,8,9), (9, 10)という形で値がまとめられ

ています。まとめられた値の最初の数を見ると 1, 3, 5, 7, 9というように skip引数で指定した 2

が公差になっている数列になっていることが確認できます。

skip引数に count引数より大きな値を指定することも可能です。コード例と実行結果を下記に示

します。

// 1~10の値を発行する IObservable<int>のシーケンス

Observable.Range(1, 10)

// 3つずつの値に分けて、値は 5つ飛ばし

.Buffer(3, 5)

.Subscribe(

l =>

{

// IList<int>の内容を出力

Console.WriteLine("-- Buffer start");

foreach (var i in l)

{

Console.WriteLine(i);

}

},

// 完了

() => Console.WriteLine("OnCompleted"));

Page 125: Reactive extensions入門v0.1

118

このコードでは countに 3, skipに 5を指定しています。実行結果を下記に示します。

-- Buffer start

1

2

3

-- Buffer start

6

7

8

OnCompleted

4.10.2. 時間でまとめる Bufferメソッドのオーバーロード

次に、時間で値をまとめる Bufferメソッドのオーバーロードについて説明します。Reactive

Extensionsが得意とする時間を扱うオーバーロードなので、単純に数を指定するだけのよりも実

践では使う機会が多いかもしれません。

まず、一番単純な時間の間隔を指定するオーバーロードのメソッドのシグネチャを下記に示します。

public static IObservable<IList<T>> Buffer<TSource>(

this IObservable<T> source,

TimeSpan timeSpan);

timeSpan引数で指定した間隔で値をためて、たまった値の IList<T>を後続に流します。コード

例を下記に示します。

var gate = new EventWaitHandle(false, EventResetMode.AutoReset);

Observable

// 500msごとに値を発行する

.Interval(TimeSpan.FromMilliseconds(500))

// 3秒間値を溜める

.Buffer(TimeSpan.FromSeconds(3))

// 最初の 3つを後続に流す

.Take(3)

.Subscribe(

l =>

{

// 値を表示

Console.WriteLine("--Buffer {0:HH:mm:ss}", DateTime.Now);

foreach (var i in l)

{

Console.WriteLine(i);

Page 126: Reactive extensions入門v0.1

119

}

},

() =>

{

// 完了

Console.WriteLine("OnCompleted");

gate.Set();

});

// OnCompleted待ち

Console.WriteLine("WaitOne");

gate.WaitOne();

Console.WriteLine("WaitOne Completed");

500msごとに値を発行して、それを 3秒スパンでまとめて出力しています。実行結果を下記に示

します。

WaitOne

--Buffer 20:35:26

0

1

2

3

4

5

--Buffer 20:35:29

6

7

8

9

10

11

--Buffer 20:35:32

12

13

14

15

16

17

Page 127: Reactive extensions入門v0.1

120

OnCompleted

WaitOne Completed

3秒間隔で 6個ずつ値がまとめられていることが確認できます。

時間指定のオーバーロードにも、第二引数で次の値の塊を作るために待機する時間を指定するオー

バーロードがあります。シグネチャを下記に示します。

public static IObservable<IList<TSource>> Buffer<T>(this IObservable<T> source,

TimeSpan timeSpan,

TimeSpan timeShift);

timeShift引数が、次の値を集め始めるまでの時間を指定する箇所になります。このオーバーロー

ドを使用したコード例を下記に示します。

var gate = new EventWaitHandle(false, EventResetMode.AutoReset);

Observable

// 500msごとに値を発行する

.Interval(TimeSpan.FromMilliseconds(500))

// 3秒間値を溜める。次の値をためるための待機時間は 2秒

.Buffer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(2))

// 最初の 3つを後続に流す

.Take(3)

.Subscribe(

l =>

{

// 値を表示

Console.WriteLine("--Buffer {0:HH:mm:ss}", DateTime.Now);

foreach (var i in l)

{

Console.WriteLine(i);

}

},

() =>

{

// 完了

Console.WriteLine("OnCompleted");

gate.Set();

});

// OnCompleted待ち

Console.WriteLine("WaitOne");

Page 128: Reactive extensions入門v0.1

121

gate.WaitOne();

Console.WriteLine("WaitOne Completed");

このコードでは、3秒間値を溜めるとともに次の値を溜め始めるまでの間隔を 2秒にしています。

もととなる IObservableのシーケンスでは 500msごとに値が発行されているため、1秒分の値が

1つ前の値の塊と重複します。実行結果を以下に示します。

WaitOne

--Buffer 20:44:00

0

1

2

3

4

5

--Buffer 20:44:02

4 ← 1秒分(500msスパンなのでこの場合 2個)の値がかぶってる

5 ←

6

7

8

9

--Buffer 20:44:04

8 ← 1秒分(500msスパンなのでこの場合 2個)の値がかぶってる

9 ←

10

11

12

13

OnCompleted

実行結果内にも示していますが、1秒分の値が重複していることが確認できます。

4.10.3. 任意のタイミングで値をまとめる Buffer メソッドのオーバーロード

これまで個数と時間で値をまとめる Bufferメソッドのオーバーロードを見てきましたが、任意の

タミングで値をまとめるのを区切るオーバーロードもあります。シグネチャを下記に示します。

public static IObservable<IList<TSource>> Buffer<T, TBufferClosing>(this IObservable<T> source,

Func<IObservable<TBufferClosing>> bufferClosingSelector);

bufferClosingSelectorデリゲートが返す IObservable<TBufferClosing>が OnNextを発行した

タミングで値をまとめるのを辞めます。コード例を下記に示します。

Page 129: Reactive extensions入門v0.1

122

var gate = new EventWaitHandle(false, EventResetMode.AutoReset);

Observable

// 500ms間隔で値を発行

.Interval(TimeSpan.FromMilliseconds(500))

// 任意の値で値をまとめるのを辞める(この場合 3秒間隔)

.Buffer(() => Observable.Interval(TimeSpan.FromSeconds(3)))

// 最初の 3つだけ後続に流す

.Take(3)

.Subscribe(

l =>

{

// 値を表示

Console.WriteLine("--Buffer {0:HH:mm:ss}", DateTime.Now);

foreach (var i in l)

{

Console.WriteLine(i);

}

},

() =>

{

// 完了

Console.WriteLine("OnCompleted");

gate.Set();

});

// OnCompleted待ち

Console.WriteLine("WaitOne");

gate.WaitOne();

Console.WriteLine("WaitOne Completed");

このコード例では、Buffer(() => Observable.Interval(TimeSpan.FromSeconds(3)))のように

して、3秒で値をまとめるのを終了するようにしています。実行結果を下記に示します。

WaitOne

--Buffer 21:08:55

0

1

2

3

4

Page 130: Reactive extensions入門v0.1

123

5

--Buffer 21:08:58

6

7

8

9

10

11

--Buffer 21:09:01

12

13

14

15

16

17

OnCompleted

WaitOne Completed

指定した通り、3秒間隔で値をまとめていることが確認できます。

この Bufferの引数に渡したデリゲートは、Bufferを開始するタミングで毎回評価されるので、

その時の状況に応じた終了タミングを表す IObservable<TBufferClosing>を返すことが出来

ます。例えば、ボタンのクリックベントを IObservableにしたものを渡すと、クリックのタ

ミングで値のグルーピングを終了させるといった使い方も出来ます。

また、IObservableを受け取るオーバーロードには値を集めるタミングと、値を集めるのを終

了するタミングを任意に指定するオーバーロードも定義されています。シグネチャを下記に示し

ます。

public static IObservable<IList<T>> Buffer<T, TBufferOpening, TBufferClosing>(

this IObservable<T> source,

IObservable<TBufferOpening> bufferOpenings,

Func<TBufferOpening, IObservable<TBufferClosing>> bufferClosingSelector);

bufferOpeningsは、もとになる IObservable<T>のシーケンスから値をあつめはじめるタミン

グを指定します。bufferOpeningsから値が発行されると bufferClosingSelectorに、その値が渡

され、値の収集を終了するタミングを通知する IObservable<TBufferClosing>が作成されます。

このオーバーロードの使用例を下記に示します。

var gate = new EventWaitHandle(false, EventResetMode.AutoReset);

// クリックをエミュレート

var clickEmuration = new Subject<Unit>();

Observable

// 500ms間隔で値を発行

Page 131: Reactive extensions入門v0.1

124

.Interval(TimeSpan.FromMilliseconds(500))

// 任意の値で値をまとめるのを辞める(この場合 3秒間隔)

.Buffer(

// clickEmurationから通知がきたら

clickEmuration.AsObservable(),

// 2秒間値を集める

_ => Observable.Interval(TimeSpan.FromSeconds(2)))

// 最初の 2つだけ後続に流す

.Take(2)

.Subscribe(

l =>

{

// 値を表示

Console.WriteLine("--Buffer {0:HH:mm:ss}", DateTime.Now);

foreach (var i in l)

{

Console.WriteLine(i);

}

},

() =>

{

// 完了

Console.WriteLine("OnCompleted");

gate.Set();

});

// Enterを押すとクリックを模した Subjectから通知を上げる

Console.ReadLine();

Console.WriteLine("{0:HH:mm:ss} Click emurate", DateTime.Now);

clickEmuration.OnNext(Unit.Default);

// Enterを押すとクリックを模した Subjectから通知を上げる

Console.ReadLine();

Console.WriteLine("{0:HH:mm:ss} Click emurate", DateTime.Now);

clickEmuration.OnNext(Unit.Default);

// OnCompleted待ち

Page 132: Reactive extensions入門v0.1

125

gate.WaitOne();

Console.WriteLine("WaitOne Completed");

このコードは、clickEmurationから値が発行されてから 2秒間 IObservable<long>のシーケン

スから値を収集して出力します。このコード例では Subject<Unit>を使用していますが、これを

ボタンのクリックベントや、何か別の契機を表す IObservableに変えることができることを考

えると、かなり柔軟性の高いオーバーロードだと言えます。実行結果を下記に示します。

21:22:02 Click emurate

--Buffer 21:22:04

5

6

7

8

21:22:08 Click emurate

--Buffer 21:22:10

17

18

19

20

OnCompleted

WaitOne Completed

4.10.4. 時間と数でまとめる Buffer メソッドのオーバーロード

最後に、紹介するのは時間と数の両方でまとめる Bufferメソッドのオーバーロードです。これは、

「4.10.1 数でまとめる Bufferメソッドのオーバーロード」と「4.10.2 時間でまとめる Buffer

メソッドのオーバーロード」で紹介した内容をあわせたもので、指定した時間か指定した数のどち

らかがそろったタミングで後続にまとめた値を流します。メソッドのシグネチャを下記に示しま

す。

public static IObservable<IList<T>> Buffer<T>(this IObservable<T> source,

TimeSpan timeSpan,

int count);

timeSpanで時間間隔を指定し、countでまとめる個数を指定します。コード例を下記に示します。

var gate = new EventWaitHandle(false, EventResetMode.AutoReset);

Observable

// 0-9の値を i * 200 ms間隔で発行する

.Generate(

0,

i => i < 10,

Page 133: Reactive extensions入門v0.1

126

i => ++i,

i => i,

i => TimeSpan.FromMilliseconds(i * 200))

// 2秒間隔か 2つ値がくるまでまとめる

.Buffer(TimeSpan.FromSeconds(2), 2)

.Subscribe(

l =>

{

// 値を表示

Console.WriteLine("--Buffer {0:HH:mm:ss}", DateTime.Now);

foreach (var i in l)

{

Console.WriteLine(i);

}

},

() =>

{

// 完了

Console.WriteLine("OnCompleted");

gate.Set();

});

gate.WaitOne();

実行結果を下記に示します。

--Buffer 21:44:37

0

1

--Buffer 21:44:38

2

3

--Buffer 21:44:40

4

5

--Buffer 21:44:42

6

--Buffer 21:44:44

7

--Buffer 21:44:46

Page 134: Reactive extensions入門v0.1

127

8

9

--Buffer 21:44:46

OnCompleted

最初の方は、値の発行間隔が短いため count引数で指定した 2つの値が流れてきていますが、後

半になってくると、値の発行間隔が長くなってくるため、timeSpanで指定した間隔で値がまとめ

られていることが確認できます。

4.10.5. Window メソッド

ここではWindowメソッドについて説明します。Windowメソッドは、ここまで説明してきた

Bufferメソッドとほぼ同じ動きをします。指定した時間や数や間隔で柔軟に値をまとめることが

出来ます。Bufferメソッドとの違いを比べるために数でまとめる一番シンプルなオーバーロード

を並べて以下に示します。

まずは、Bufferメソッドです。

public static IObservable<IList<T>> Buffer<T>(

this IObservable<T> source,

int count);

次に、Windowメソッドを示します。

public static IObservable<IObservable<T>> Window<T>(

this IObservable<T> source,

int count);

Bufferメソッドの戻り値が IObservable<IList<T>>なのに対してWindowメソッドは

IObservable<IObservable<T>>になります。このシグネチャの違いが挙動にどう影響している

のかを比較するためのコードを下記に示します。

まずは、これまで使ってきた Bufferメソッドのコード例を下記に示します。

// 値の発行元

var source = new Subject<int>();

source

// 3つずつに纏める

.Buffer(3)

.Subscribe(

l =>

{

// 値を表示

Console.WriteLine("--Buffer");

// IList<T>なのでループを使って値を出力

foreach (var i in l)

{

Page 135: Reactive extensions入門v0.1

128

Console.WriteLine(i);

}

},

() =>

{

// 完了通知

Console.WriteLine("Buffer Completed");

});

// 1~4の値を発行して終了

Console.WriteLine("OnNext(1)");

source.OnNext(1);

Console.WriteLine("OnNext(2)");

source.OnNext(2);

Console.WriteLine("OnNext(3)");

source.OnNext(3);

Console.WriteLine("OnNext(4)");

source.OnNext(4);

Console.WriteLine("OnCompleted()");

source.OnCompleted();

Subject<int>を使って、1~4の値を発行しています。そして、Bufferメソッドで 3つずつの値

にまとめて表示しています。実行結果を以下に示します。

OnNext(1)

OnNext(2)

OnNext(3)

--Buffer

1

2

3

OnNext(4)

OnCompleted()

--Buffer

4

Buffer Completed

実行結果からわかるように、IObservable<int>のシーケンスから値が 3つ発行されると Buffer

の Subscribeの OnNextが呼ばれて値が列挙されています。IObservable<int>のシーケンスが終

了すると、残った値をまとめて Bufferの Subscribeの OnNextが呼ばれます。これが Bufferメ

Page 136: Reactive extensions入門v0.1

129

ソッドでの挙動です。次に、このコードの Bufferメソッドの部分をWindowメソッドにしたコー

ドを示します。

// 値の発行元

var source = new Subject<int>();

source

// 3つずつに纏める

.Window(3)

.Subscribe(

o =>

{

// 値を表示

Console.WriteLine("--Window");

// IO<T>なので Subscribeで OnNextを使って出力する

o.Subscribe(Console.WriteLine);

},

() =>

{

// 完了通知

Console.WriteLine("Window Completed");

});

// 1~4の値を発行して終了

Console.WriteLine("OnNext(1)");

source.OnNext(1);

Console.WriteLine("OnNext(2)");

source.OnNext(2);

Console.WriteLine("OnNext(3)");

source.OnNext(3);

Console.WriteLine("OnNext(4)");

source.OnNext(4);

Console.WriteLine("OnCompleted()");

source.OnCompleted();

BufferメソッドをWindowメソッドに変えたのと、Subscribeの OnNextで渡ってくる型が

IObservable<int>になっているためループから Subscribeに書き換えています。このコードの実

行結果を以下に示します。

--Window

OnNext(1)

Page 137: Reactive extensions入門v0.1

130

1

OnNext(2)

2

OnNext(3)

3

--Window

OnNext(4)

4

OnCompleted()

Window Completed

Bufferメソッドが、値が 3つ揃ってから IList<int>という形で後続に値を流しているのに対して

Windowメソッドは、値が 3つ揃う前から即座に後続に値を流していることが確認できます。

Windowメソッドの戻り値が IObservable<IObservable<T>>になっているため、リゕルタム

に値を監視して処理することが出来るようになっています。

そのため Bufferメソッドは、値が全て揃ってから後続に値を流したい場合に、Windowメソッド

は、値が発行されたら即座に後続に値を流したい場合に利用します。その他のオーバーロードに関

しても挙動は同じになります。そのため、ここではその他のWindowメソッドのオーバーロード

についての説明は割愛します。

4.11. 発行された値にたいして時間でフィルタ・操作するメソッド

ここでは、IObservable<T>のシーケンスから発行される値に対して時間で何かしらの影響を与

えるメソッドの説明を行います。

4.11.1. Sample メソッド

Sampleメソッドについて説明します。Sampleメソッドは、指定した間隔(時間や任意のタミ

ング)で最後に発行された値を後続に流すメソッドになります。大量の値が発行されるような

IObservable<T>のシーケンスから任意の間隔で値を絞って後続の処理に渡すケースなどで利用

できます。

メソッドのシグネチャを下記に示します。

public static IObservable<T> Sample<T>(

this IObservable<T> source,

TimeSpan interval);

このオーバーロードは、時間で値をフゖルタリングします。もう1つ任意の間隔で後続に最後の値

を流すオーバーロードは下記のようなシグネチャになっています。

public static IObservable<T> Sample<T, TSample>(

this IObservable<T> source,

IObservable<TSample> sampler);

Page 138: Reactive extensions入門v0.1

131

このオーバーロードは、IObservable<TSample>の OnNextで値が発行されるタミングで後続

に値を流します。このオーバーロードを使うことで、非同期処理が終わったタミングや、ボタン

のクリックベントなどをトリガーにして、一番最後に発行された値を後続に流せます。

コード例を以下に示します。

var r = new Random();

var subscriber = Observable

// 100msの間隔で

.Interval(TimeSpan.FromMilliseconds(100))

// 0~99の乱数を発生させる

.Select(_ => r.Next(100))

// 値が発行されたことを確認するためのダンプ

.Do(i => Console.WriteLine("{0:HH:mm:ss} Dump {1}", DateTime.Now, i))

// 1秒間隔で最後に発行された値を後続に流す

.Sample(TimeSpan.FromMilliseconds(1000))

// 購読

.Subscribe(

// 値を表示

i => Console.WriteLine("{0:HH:mm:ss} OnNext {1}", DateTime.Now, i));

Console.WriteLine("Please enter key");

Console.ReadLine();

// 購読終了

subscriber.Dispose();

100ミリ秒間隔で 0~99の値を発行しています。この値を Sampleメソッドを使って 1秒間隔で

最後に発行された値に絞り込んで Subscribeで結果を表示しています。実行結果を以下に示しま

す。

Please enter key

23:13:23 Dump 63

23:13:23 Dump 0

23:13:23 Dump 36

23:13:23 Dump 18

23:13:23 Dump 53

23:13:24 Dump 65

23:13:24 Dump 63

23:13:24 Dump 95

23:13:24 Dump 39

23:13:24 Dump 44

23:13:24 OnNext 44

Page 139: Reactive extensions入門v0.1

132

23:13:24 Dump 72

23:13:24 Dump 91

23:13:24 Dump 36

23:13:24 Dump 37

23:13:24 Dump 68

23:13:25 Dump 69

23:13:25 Dump 98

23:13:25 Dump 67

23:13:25 Dump 79

23:13:25 Dump 23

23:13:25 OnNext 23

23:13:25 Dump 6

23:13:25 Dump 53

23:13:25 Dump 22

23:13:25 Dump 47

23:13:25 Dump 94

23:13:26 Dump 67

23:13:26 Dump 99

23:13:26 Dump 2

23:13:26 Dump 70

23:13:26 Dump 76

23:13:26 OnNext 70

23:13:26 Dump 82

長い実行結果になりますが、Dumpが大量に表示されていることから 100msごとに乱数が発生し

ていることが確認できます。その中で Subscribeの OnNextまで渡っている値(実行結果の中で太

字の赤文字にしている箇所)は 1秒間隔になっていることが確認できます。

上記の例では、発行される値の間隔に比べて Sampleで取得する値をフゖルタリングするように

設定していましたが、逆に値は 10秒間隔で発行されるのに対して Sampleメソッドで 8秒でフゖ

ルタリングしたときに、どのような挙動になるのかを確認したいと思います。コード例を下記に示

します。

var r = new Random();

var subscriber = Observable

// 10秒間隔で

.Interval(TimeSpan.FromSeconds(10))

// 乱数を発生させる

.Select(_ => r.Next(100))

// 発生した乱数を表示

Page 140: Reactive extensions入門v0.1

133

.Do(i => Console.WriteLine("{0:HH:mm:ss} Dump {1}", DateTime.Now, i))

// 8秒間隔でフゖルタリングする

.Sample(TimeSpan.FromSeconds(8))

.Subscribe(

// 渡ってきた値を表示する

i => Console.WriteLine("{0:HH:mm:ss} OnNext {1}", DateTime.Now, i));

Console.WriteLine("Please enter key");

Console.ReadLine();

// 購読解除

subscriber.Dispose();

実行結果は以下のようになります。

Please enter key

23:23:20 Dump 1

23:23:20 OnNext 1

23:23:22 Dump 39

23:23:22 OnNext 39

23:23:24 Dump 91

23:23:24 OnNext 91

23:23:26 Dump 61

23:23:26 OnNext 61

結果からわかるように、Sampleメソッドは、8秒待っても値が発行されない(元のシーケンスは

10秒間隔での値の発行のため)ので、値が発行されなかった場合は何もせず次のタミングを待

っています、次のタミングで値がきていたら後続に値を流す動きをします。

IObservable<TSample>を渡して任意のタミングで、後続に値を流すメソッドのオーバーロー

ドについては、割愛します。

4.11.2. Throttle メソッド

ここでは、Throttleメソッドについて説明します。Throttleメソッドは指定した間、新たな値が

発行されなかったら最後に発行された値を後続に流すメソッドです。メソッドのシグネチャを以下

に示します。

public static IObservable<T> Throttle<T>(

this IObservable<T> source,

TimeSpan dueTime);

第二引数の dueTimeで後続に値を流すための判断基準になる間隔を指定します。このメソッドの

使用例を下記に示します。

var source = new Subject<int>();

// 500ms値が発行されなかったら最後に発行された値を後続に流す

Page 141: Reactive extensions入門v0.1

134

source

.Throttle(TimeSpan.FromMilliseconds(500))

// 渡ってきた値を時間つきで表示

.Subscribe(i =>

Console.WriteLine("{0:HH:mm:ss.fff} {1}", DateTime.Now, i));

// 100ms間隔で値を発行

foreach (var i in Enumerable.Range(1, 10))

{

// 発行した値を出力しておく

Console.WriteLine("{0:HH:mm:ss.fff} OnNext({1})",DateTime.Now, i);

source.OnNext(i);

Thread.Sleep(100);

}

// 2000ms sleep

Console.WriteLine("{0:HH:mm:ss.fff} Sleep(2000)", DateTime.Now);

Thread.Sleep(2000);

// 100ms間隔で値を発行

foreach (var i in Enumerable.Range(1, 5))

{

// 発行した値を出力しておく

Console.WriteLine("{0:HH:mm:ss.fff} OnNext({1})", DateTime.Now, i);

source.OnNext(i);

Thread.Sleep(100);

}

// 2000ms sleep

Console.WriteLine("{0:HH:mm:ss.FFF} Sleep(2000)", DateTime.Now);

Thread.Sleep(2000);

Throttleメソッドで500msの間、値が発行されなかった場合に後続に値を流すようにしています。

そのあと、foreachで値を 10個 100ms間隔で発行して 2000msスリープしています。次に、値

を 5個 100ms間隔で発行して、2000msスリープしています。実行結果を以下に示します。

23:05:36.003 OnNext(1)

23:05:36.116 OnNext(2)

23:05:36.217 OnNext(3)

Page 142: Reactive extensions入門v0.1

135

23:05:36.317 OnNext(4)

23:05:36.417 OnNext(5)

23:05:36.517 OnNext(6)

23:05:36.617 OnNext(7)

23:05:36.717 OnNext(8)

23:05:36.817 OnNext(9)

23:05:36.917 OnNext(10)

23:05:37.023 Sleep(2000)

23:05:37.420 10

23:05:39.026 OnNext(1)

23:05:39.126 OnNext(2)

23:05:39.226 OnNext(3)

23:05:39.326 OnNext(4)

23:05:39.426 OnNext(5)

23:05:39.526 Sleep(2000)

23:05:39.926 5

実行結果で太字の赤文字にしている箇所が、Subscribeで出力している箇所です。直前の OnNext

から 500ms後に出力されていることが確認できます。

4.11.3. Delayメソッド

ここでは、Delayメソッドについて説明します。Delayメソッドは名前が示す通り

IObservable<T>のシーケンスから発行された値を指定した時間だけ遅延させて後続に流すメソ

ッドになります。

メソッドのシグネチャを以下に示します。

public static IObservable<T> Delay<T>(

this IObservable<T> source,

TimeSpan dueTime);

引数の dueTimeで遅延させる時間を指定します。TimeSpan型を受け取る以外にも、特定の時点

まで遅延させることも出来ます。このメソッドのコード例を下記に示します。

var source = new Subject<int>();

source

// 10秒遅延させる

.Delay(TimeSpan.FromSeconds(10))

// 値を時間つきで表示させる

.Subscribe(i =>

Console.WriteLine("{0:HH:mm:ss.fff} {1}", DateTime.Now, i));

// 1秒間隔で 1~5の値を発行

Page 143: Reactive extensions入門v0.1

136

foreach (var i in Enumerable.Range(1, 5))

{

Console.WriteLine("{0:HH:mm:ss.fff} OnNext({1})", DateTime.Now, i);

source.OnNext(i);

Thread.Sleep(1000);

}

// 終了待ち

Console.WriteLine("Please enter key");

Console.ReadLine();

Delayメソッドを使って IObservable<int>のシーケンスから発行された値を 10秒遅延させてい

ます。実行結果を以下に示します。

21:58:12.174 OnNext(1)

21:58:13.202 OnNext(2)

21:58:14.202 OnNext(3)

21:58:15.202 OnNext(4)

21:58:16.202 OnNext(5)

Please enter key

21:58:22.214 1

21:58:23.205 2

21:58:24.202 3

21:58:25.216 4

21:58:26.215 5

実行結果からわかるように、OnNextで発行した値が 10秒後に表示されています。このように

Delayメソッドを使うと、値を遅延させて後続に流すということが実現できます。

4.11.4. Timeout メソッド

ここでは、Timeoutメソッドについて説明します。Timeoutメソッドは名前の通り

IObservable<T>のシーケンスから指定した時間、値が発行されなかった場合にエラーにするメ

ソッドです。シグネチャを以下に示します。

public static IObservable<T> Timeout<T>(

this IObservable<T> source,

TimeSpan dueTime);

dueTimeでタムゕウトの時間を指定します。TimeSpan以外にも DateTimeOffsetでタムゕ

ウトを指定するオーバーロードがありますが、ここでは紹介を割愛します。このメソッドのコード

例を下記に示します。

var subscriber = Observable

// 0~4の値を i秒間隔で発行する

Page 144: Reactive extensions入門v0.1

137

.Generate(0, i => i < 5, i => i + 1, i => i, i => TimeSpan.FromSeconds(i))

// 3500ms以上間隔があくとタムゕウト

.Timeout(TimeSpan.FromMilliseconds(3500))

// 購読

.Subscribe(

i => Console.WriteLine("{0:HH:mm:ss} OnNext({1})", DateTime.Now, i),

ex => Console.WriteLine("{0:HH:mm:ss} OnError({1})", DateTime.Now, ex),

() => Console.WriteLine("{0:HH:mm:ss} OnCompleted()", DateTime.Now));

// Enterを押すと購読終了

Console.ReadLine();

subscriber.Dispose();

このコードでは、Generateメソッドを使って 0, 1, 2, 3, 4の値を 0s, 1s, 2s, 3s, 4s間隔で発行

しています。それに対して Timeoutメソッドで 3.5s(3500ms)を指定しています。このコードの

実行結果を以下に示します。

12:39:48 OnNext(0)

12:39:49 OnNext(1)

12:39:51 OnNext(2)

12:39:54 OnNext(3)

12:39:58 OnError(System.TimeoutException: 操作がタムゕウトしました。)

最後の 4が発行されるのに 4秒間が空いてしまうので、TimeoutExceptionが発行され Subscribe

の OnErrorに処理がいきます。このように、時間を要する処理のタムゕウトを簡単に指定する

ことが出来ます。

この Timeoutメソッドにはタムゕウト時の動作をカスタマズすることが出来るオーバーロー

ドがあります。そのメソッドのシグネチャを以下に示します。

public static IObservable<T> Timeout<T>(

this IObservable<T> source,

TimeSpan dueTime,

IObservable<T> other);

第三引数の otherで、タムゕウトが起きたときに代わりに値を発行する IObservable<T>のシ

ーケンスを指定します。このオーバーロードの使用例を下記に示します。

var subscriber = Observable

// 0~4の値を i秒間隔で発行する

.Generate(0, i => i < 5, i => i + 1, i => i, i => TimeSpan.FromSeconds(i))

// 3500ms以上間隔があくとタムゕウト

.Timeout(

TimeSpan.FromMilliseconds(3500),

// タムゕウトの時に流す値を指定

Observable.Create<int>(o =>

Page 145: Reactive extensions入門v0.1

138

{

Console.WriteLine("{0:HH:mm:ss} Error Action", DateTime.Now);

// -1を流して完了

o.OnNext(-1);

o.OnCompleted();

return Disposable.Empty;

}))

// 購読

.Subscribe(

i => Console.WriteLine("{0:HH:mm:ss} OnNext({1})", DateTime.Now, i),

ex => Console.WriteLine("{0:HH:mm:ss} OnError({1})", DateTime.Now, ex),

() => Console.WriteLine("{0:HH:mm:ss} OnCompleted()", DateTime.Now));

// Enterを押すと購読終了

Console.ReadLine();

subscriber.Dispose();

Timeoutメソッドで Observable.Createメソッドを使ってタムゕウトの時に-1の値を流して

IObservable<T>のシーケンスを完了させるようにしています。このコードの実行結果を以下に

示します。

13:08:09 OnNext(0)

13:08:10 OnNext(1)

13:08:12 OnNext(2)

13:08:15 OnNext(3)

13:08:19 Error Action

13:08:19 OnNext(-1)

13:08:19 OnCompleted()

最初のコード例では TimeoutExceptionが発生していましたが、この例では Observable.Create

で作成した IObservableから発行された値が後続に流れていることが確認できます。このように

タムゕウトとタムゕウトに伴って発行する値を差し替えたりすることが出来ます。

4.12. 時間に関する情報を付与する拡張メソッド

ここでは、IObservable<T>のシーケンスに対して時間に関する情報を付与する

IObservable<T>の拡張メソッドについて説明します。

Page 146: Reactive extensions入門v0.1

139

4.12.1. Timestampメソッド

ここでは、Timesptampメソッドについて説明します。Timestampメソッドは、IObservable<T>

のシーケンスに対して、その名の通りタムスタンプを追加するメソッドです。メソッドのシグネ

チャを以下に示します。

public static IObservable<Timestamped<T>> Timestamp<T>(this IObservable<T> source);

戻り値が IObservable<Timestamped<T>>型になっています。この Timestamped<T>型は以

下のように定義されています。(演算子のオーバーロード等は省いています)

using System;

namespace System.Reactive

{

public struct Timestamped<T>

{

public DateTimeOffset Timestamp { get; }

public T Value { get; }

}

}

Timestampプロパテゖで、時間を取得しValueプロパテゖで値を取得するシンプルな構造体です。

コード例を下記に示します。

var subscriber = Observable

// 1秒間隔で 0~4の値を発行

.Generate(0, i => i < 5, i => i + 1, i => i, _ => TimeSpan.FromSeconds(1))

// タムスタンプをつける(Timestamped<T>型になる)

.Timestamp()

// 購読

.Subscribe(

i => Console.WriteLine("Timestamp: {0}, Value: {1}", i.Timestamp, i.Value));

// 終了待ち

Console.WriteLine("Please enter key.");

Console.ReadLine();

subscriber.Dispose();

1秒間隔で 0~4の値を発行して、それに Timestampメソッドを呼び出して Timestamped<T>

型に変換しています。そして Subscribeで Timestampと Valueを表示しています。実行結果を

以下に示します。

Please enter key.

Page 147: Reactive extensions入門v0.1

140

Timestamp: 2012/02/05 22:47:59 +09:00, Value: 0

Timestamp: 2012/02/05 22:48:00 +09:00, Value: 1

Timestamp: 2012/02/05 22:48:01 +09:00, Value: 2

Timestamp: 2012/02/05 22:48:02 +09:00, Value: 3

Timestamp: 2012/02/05 22:48:03 +09:00, Value: 4

値が発行された時の時間が表示されていることが確認できます。

4.12.2. TimeInterval メソッド

ここでは、TimeIntervalメソッドについて説明します。TimeIntervalメソッドは

IObservable<T>のシーケンスから発行された値の間隔を TimeSpan型で追加します。メソッド

のシグネチャを以下に示します。

public static IObservable<TimeInterval<T>> TimeInterval<T>(this IObservable<T> source);

戻り値が、IObservable<TimeInterval<T>>型になっています。TimeInterval<T>型は以下の

ように定義されています。(演算子のオーバーロード等は省いています)

using System;

namespace System.Reactive

{

public struct TimeInterval<T>

{

public TimeSpan Interval { get; }

public T Value { get; }

}

}

Intervalプロパテゖで時間間隔を取得し、Valueプロパテゖで値を取得するシンプルな構造体です。

コード例を下記に示します。

var subscriber = Observable

// 1秒間隔で 0~4の値を発行

.Generate(0, i => i < 5, i => i + 1, i => i, _ => TimeSpan.FromSeconds(1))

// 値の発行間隔をつける(TimeInterval<T>型になる)

.TimeInterval()

// 購読

.Subscribe(

i => Console.WriteLine("Interval: {0}, Value: {1}", i.Interval, i.Value));

// 終了待ち

Console.WriteLine("Please enter key.");

Page 148: Reactive extensions入門v0.1

141

Console.ReadLine();

subscriber.Dispose();

1秒間隔で 0~4の値を発行して、それに TimeIntervalメソッドを呼び出して TimeInterval<T>

型に変換しています。そして Subscribeで Intervalと Valueを表示しています。実行結果を以下

に示します。

Please enter key.

Interval: 00:00:01.0050575, Value: 0

Interval: 00:00:01.0150581, Value: 1

Interval: 00:00:01.0140580, Value: 2

Interval: 00:00:01.0140580, Value: 3

Interval: 00:00:01.0120578, Value: 4

値が発行された間隔が表示されていることが確認できます。

4.13. 型変換を行う拡張メソッド

ここでは、IObservable<T>のシーケンスに対して型変換を行うメソッドについて説明します。

4.13.1. Cast メソッド

ここでは Castメソッドについて説明します。Castメソッドは名前の通り、もとになる

IObservable<object>のシーケンスから発行された値をキャストして後続に流します。メソッド

のシグネチャを以下に示します。

public static IObservable<T> Cast<T>(this IObservable<object> source);

このメソッドの使用例を下記に示します。

var source = new Subject<object>();

source

// 値を int型にキャスト

.Cast<int>()

// 購読

.Subscribe(

i => Console.WriteLine("OnNext({0})", i),

ex => Console.WriteLine("OnError({0})", ex),

() => Console.WriteLine("OnCompleted()"));

// int型の値を発行

source.OnNext(1);

source.OnNext(2);

source.OnNext(3);

Page 149: Reactive extensions入門v0.1

142

// 文字列型を発行してみる

source.OnNext("abc");

Castメソッドを使って object型から int型に変換して Subscribeで購読しています。そして、1

~3の値を発行したあとに文字列型の”abc”を発行しています。実行結果を以下に示します。

OnNext(1)

OnNext(2)

OnNext(3)

OnError(System.InvalidCastException: 指定されたキャストは有効ではありません。

場所 System.Reactive.Linq.Observable.<Cast>b__46e[TResult](Object x)

場所 System.Reactive.Linq.Observable.<>c__DisplayClass408`2.<>c__DisplayClass

40a.<Select>b__407(TSource x))

実行結果からわかる通り、文字列型を発行すると InvalidCastExceptionが発行されて OnError

に処理がいっていることが確認できます。このように Castメソッドは、あくまで単純なキャスト

を行うためのメソッドであることがわかります。

4.13.2. OfTypeメソッド

ここでは、OfTypeメソッドについて説明します。OfTypeメソッドは、もとになる

IObservable<object>のシーケンスから指定した型でフゖルタリングを行うメソッドになります。

メソッドのシグネチャを以下に示します。

public static IObservable<T> OfType<T>(this IObservable<object> source);

このメソッドのコード例を下記に示します。

var source = new Subject<object>();

source

// int型のみを通過させる

.OfType<int>()

// 購読

.Subscribe(

i => Console.WriteLine("OnNext({0})", i),

ex => Console.WriteLine("OnError({0})", ex),

() => Console.WriteLine("OnCompleted()"));

// int型の値を発行

source.OnNext(1);

source.OnNext(2);

source.OnNext(3);

// 文字列型を発行してみる

Page 150: Reactive extensions入門v0.1

143

source.OnNext("abc");

source.OnNext("4");

// もう一度 int型の値を発行して終了

source.OnNext(100);

source.OnCompleted();

OfTypeメソッドを使って、1~3の値が発行されたあと文字列型で”abc”と”4”が発行されて、100

が発行されるシーケンスをフゖルタリングして、購読しています。実行結果を以下に示します。

OnNext(1)

OnNext(2)

OnNext(3)

OnNext(100)

OnCompleted()

Castメソッドとは異なり、文字列型が発行されても例外は発生しません。単純に後続に値が流れ

ることなくフゖルタリングされます。

4.14. Cold から Hot へ変換する拡張メソッド

ここでは、Coldな IObservable<T>のシーケンスを Hotな IObservable<T>のシーケンスに変

換するメソッドについて説明します。Coldから Hotに変換することで、複数回 Subscribeしたと

きに、毎回値を発行するのか、まとめて値を発行するのかといったことを細かく制御することが可

能になります。

4.14.1. Publish メソッド

ここでは、Publishメソッドについて説明します。Publishメソッドは、Coldな IObservable<T>

を Hotな IObservable<T>に変換するメソッドです。Coldな IObservable<T>は、Subscribe

で追加された Observerごとに独立して値を流し込みます。しかし、この Publishメソッドを使う

ことで、複数の Observerに対して同時に同一の値を流すことが出来ます。Publishメソッドのシ

グネチャを以下に示します。

public static IConnectableObservable<T> Publish<T>(this IObservable<T> source);

Publishメソッドの戻り値の IConnectableObservable<T>は、Connectというメソッドを持っ

た IObservable<T>になります。この Connectメソッドを呼び出すまで、元になった

IObservable<T>の値を発行するのを遅延させます。この Connectメソッドのシグネチャを以下

に示します。

IDisposable Connect();

Connectメソッドは、戻り値の IDisposableに対して Disposeメソッドが呼び出されるまで Hot

な IObservable<T>として振る舞います。

コード例を下記に示します。

// coldな Observableの作成

Page 151: Reactive extensions入門v0.1

144

var source = Observable.Range(1, 3);

// 2回購読

Console.WriteLine("# Subscribe1");

source.Subscribe(

i => Console.WriteLine("Subscribe1#OnNext: {0}", i),

() => Console.WriteLine("Subscribe1#OnCompleted"));

Console.WriteLine("# Subscribe2");

source.Subscribe(

i => Console.WriteLine("Subscribe2#OnNext: {0}", i),

() => Console.WriteLine("Subscribe2#OnCompleted"));

Console.WriteLine("--");

// Publishで Hotな Observableに

Console.WriteLine("# Publish");

var connectableSource = source.Publish();

// 2回購読

Console.WriteLine("# Subscribe1");

connectableSource.Subscribe(

i => Console.WriteLine("Subscribe1#OnNext: {0}", i),

() => Console.WriteLine("Subscribe1#OnCompleted"));

Console.WriteLine("# Subscribe2");

connectableSource.Subscribe(

i => Console.WriteLine("Subscribe2#OnNext: {0}", i),

() => Console.WriteLine("Subscribe2#OnCompleted"));

// Connectで購読している Observerに値を流す

Console.WriteLine("# Connect");

connectableSource.Connect();

// Connect後に購読したときの動作確認

Console.WriteLine("# Subscribe3");

connectableSource.Subscribe(

i => Console.WriteLine("Subscribe3#OnNext: {0}", i),

() => Console.WriteLine("Subscribe3#OnCompleted"));

Page 152: Reactive extensions入門v0.1

145

まず、Rangeメソッドで 0~2の値を発行する Coldな IObservable<T>を作成しています。

Publishをした時としてない時とで動きを比較するために、Publishをする前に 2回 Subscribeを

しています。その後、Publishメソッドを呼び出して Hotな IObservable<T>へ変換しています。

そして、2回購読を行って Connectメソッドで値の発行を開始しています。

上記コードの実行結果を以下に示します。

# Subscribe1

Subscribe1#OnNext: 1

Subscribe1#OnNext: 2

Subscribe1#OnNext: 3

Subscribe1#OnCompleted

# Subscribe2

Subscribe2#OnNext: 1

Subscribe2#OnNext: 2

Subscribe2#OnNext: 3

Subscribe2#OnCompleted

--

# Publish

# Subscribe1

# Subscribe2

# Connect

Subscribe1#OnNext: 1

Subscribe2#OnNext: 1

Subscribe1#OnNext: 2

Subscribe2#OnNext: 2

Subscribe1#OnNext: 3

Subscribe2#OnNext: 3

Subscribe1#OnCompleted

Subscribe2#OnCompleted

# Subscribe3

Subscribe3#OnCompleted

IConnectableObservable<T>の Connectメソッドが呼ばれるまで、値を購読しているところま

で値が流れていないことが確認できます。値の発行が終わった後に、Subscribeしたときには

IObservable<T>は完了している状態のため、OnCompletedが実行されていることが確認できま

す。

Publishの動作を確認するために、もう1つコード例を見ていきます。今度は Intervalメソッドを

使って 500msごとに 0からカウントゕップしていく Coldな IObservable<T>に対して Publish

メソッドを呼び出しています。購読者のいない状態での Connectと、Connectしたものの途中か

Page 153: Reactive extensions入門v0.1

146

ら購読した場合の挙動について確認しています。また、Connectメソッドの戻り値の IDisposable

を使って Connectの解除を行っています。コードを下記に示します。

// 500ms間隔で 0からカウントゕップしていく値を発行する IObservableを作成

Console.WriteLine("# Create source(ConnectableObservable)");

var source = Observable

.Interval(TimeSpan.FromMilliseconds(500))

.Publish();

// Connectで値の放流開始

Console.WriteLine("# Connect");

using (source.Connect())

{

// 2秒間何もせずに待つ

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

// 2秒間購読

Console.WriteLine("# Subscribe1");

using (

source.Subscribe(

i => Console.WriteLine("Subscribe1#OnNext: {0}", i),

() => Console.WriteLine("Subscribe1#OnCompleted")))

{

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

}

Console.WriteLine("# UnSubscribe1");

}

// Disposeが呼ばれるので Connectが解除される

Console.WriteLine("# DisConnect");

// Connectが解除された状態で 2秒間購読する

Console.WriteLine("# Subscribe2");

using (

source.Subscribe(

i => Console.WriteLine("Subscribe2#OnNext: {0}", i),

() => Console.WriteLine("Subscribe2#OnCompleted")))

Page 154: Reactive extensions入門v0.1

147

{

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

}

Console.WriteLine("# UnSubscribe2");

// 再接続

Console.WriteLine("# Connect");

using (source.Connect())

{

// 再接続で発行される値を確認するため 2秒間購読する

Console.WriteLine("# Subscribe3");

using (

source.Subscribe(

i => Console.WriteLine("Subscribe3#OnNext: {0}", i),

() => Console.WriteLine("Subscribe3#OnCompleted")))

{

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

}

Console.WriteLine("# UnSubscribe3");

}

// Disposeが呼ばれるので接続解除

Console.WriteLine("# DisConnect");

// 接続解除状態で 2秒間待機して様子を見る

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

実行結果を以下に示します。

# Create source(ConnectableObservable)

# Connect

Sleep 2sec...

# Subscribe1

Sleep 2sec...

Subscribe1#OnNext: 4

Subscribe1#OnNext: 5

Subscribe1#OnNext: 6

Page 155: Reactive extensions入門v0.1

148

# UnSubscribe1

# DisConnect

# Subscribe2

Sleep 2sec...

# UnSubscribe2

# Connect

# Subscribe3

Sleep 2sec...

Subscribe3#OnNext: 0

Subscribe3#OnNext: 1

Subscribe3#OnNext: 2

# UnSubscribe3

# DisConnect

Sleep 2sec...

Connectをして 2秒経過してからの購読(Subscribe1)で値が 4から始まっていることが確認で

きます。このことから、Connectを呼び出したタミングで Intervalから値が発行されているこ

とがわかります。また、Connectを解除してからの購読(Subscribe2)では値が発行されていな

いことも確認できます。そして最後に、再度 Connectしてから購読(Subscribe3)では、値が 0

から始まっていることが確認できます。このことから、再 Connect時には元になる Coldな

IObservable<T>から再度値が発行されていることがわかります。

4.14.2. RefCountメソッド

ここでは、IConnectableObservable<T>の拡張メソッドとして定義されている RefCountメソッ

ドについて説明します。RefCountメソッドは、購読者が1つ以上いる時は Connect状態を維持

して購読者が 0になったら Connectを解除する IObservable<T>を返すメソッドです。このメソ

ッドを使うことで IConnectableObservable<T>のConnectメソッドと戻り値の IDisposableの

管理を省略することが出来ます。RefCountメソッドのシグネチャを以下に示します。

public static IObservable<T> RefCount<T>(this IConnectableObservable<T> source);

このメソッドの使用例を下記に示します。

// 500ms間隔で 0から値をカウントゕップしていく IObservableを作成

Console.WriteLine("# Create source(RefCount)");

var source = Observable

.Interval(TimeSpan.FromMilliseconds(500))

// Hot

.Publish()

// 購読者がいる間 Connect状態を保つ

.RefCount();

Page 156: Reactive extensions入門v0.1

149

// 2秒待機して様子身

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

// 購読開始

Console.WriteLine("# Subscribe1");

using (

source.Subscribe(

i => Console.WriteLine("Subscribe1#OnNext: {0}", i),

() => Console.WriteLine("Subscribe1#OnCompleted")))

{

// 購読状態で 2秒待機

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

// 追加で 1秒間購読

Console.WriteLine("# Subscribe2");

using (source.Subscribe(

i => Console.WriteLine("Subscribe2#OnNext: {0}", i),

() => Console.WriteLine("Subscribe2#OnCompleted")))

{

Console.WriteLine("Sleep 1sec...");

Thread.Sleep(1000);

}

// 1つ購読解除

Console.WriteLine("# UnSubscribe2");

// 1つ購読解除した状態で 2秒待機

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

}

// 購読解除(ここで購読者数が 0になるので Connectが解除される)

Console.WriteLine("# UnSubscribe1");

// 2秒待機

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

Page 157: Reactive extensions入門v0.1

150

// 新たに購読開始(Connect状態になる)

Console.WriteLine("# Subscribe3");

using (source.Subscribe(

i => Console.WriteLine("Subscribe3#OnNext: {0}", i),

() => Console.WriteLine("Subscribe3#OnCompleted")))

{

// 2秒待機

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

}

// 購読解除

Console.WriteLine("# UnSubscribe3");

usingブロックを使って購読と購読解除を複数回行っています。最初の購読(Subscribe1)の後

2秒待機して、次の購読(Subscribe2)をしています。Subscribe2は1秒後に購読を解除して、

その2秒後に Subscribe1も購読を解除しています。そして、2秒待機したあと再び購読

(Subscribe3)をしています。実行結果を以下に示します。

# Create source(RefCount)

Sleep 2sec...

# Subscribe1

Sleep 2sec...

Subscribe1#OnNext: 0

Subscribe1#OnNext: 1

Subscribe1#OnNext: 2

# Subscribe2

Sleep 1sec...

Subscribe1#OnNext: 3

Subscribe2#OnNext: 3

Subscribe1#OnNext: 4

Subscribe2#OnNext: 4

# UnSubscribe2

Sleep 2sec...

Subscribe1#OnNext: 5

Subscribe1#OnNext: 6

Subscribe1#OnNext: 7

Subscribe1#OnNext: 8

# UnSubscribe1

Page 158: Reactive extensions入門v0.1

151

Sleep 2sec...

# Subscribe3

Sleep 2sec...

Subscribe3#OnNext: 0

Subscribe3#OnNext: 1

Subscribe3#OnNext: 2

# UnSubscribe3

Subscribe1で値を出力している途中で Subscribe2も購読を開始しています。このときの出力を

見ると同じ値が同時に複数の購読者に対して発行されているので Hotな IObservable<T>になっ

ていることが確認できます。そして、Subscribe1と Subscribe2の購読を解除したあと、再度購

読(Subscribe3)をすると、値が 0からはじまっていることが確認できます。このことから、購

読者数が0になったタミングで、Connectが解除されていることがわかります。

4.14.3. 引数を受け取る Publish メソッドのオーバーロード

ここでは、引数を受け取る Publishメソッドのオーバーロードについて説明します。このオーバー

ロードは、Subscribeしたタミングで引数で渡した値を流します。その後、の動作は引数無しの

Publishメソッドと同様に Connectメソッドを呼び出したタミングで値を流します。このメソ

ッドのシグネチャを以下に示します。

public static IConnectableObservable<T> Publish<T>(

this IObservable<T> source,

TSource initialValue);

このメソッドの使用例を下記に示します。

// Publishの intの引数を受け取るオーバーロード

var source = Observable

// 1~3の値を発行する Coldな Observable

.Range(1, 3)

// initialValueに 100を指定して Publishを呼ぶ

.Publish(100);

// 購読

Console.WriteLine("# Subscribe");

source.Subscribe(

i => Console.WriteLine("OnNext: {0}", i),

() => Console.WriteLine("OnCompleted"));

// 接続

Console.WriteLine("# Connect");

source.Connect();

Page 159: Reactive extensions入門v0.1

152

Publishメソッドの引数に 100を渡して IConnectableObservable<T>を作成しています。その

後、購読して Connectメソッドを呼び出しています。Subscribeの OnNextの呼び出されるタ

ミングに注目して実行結果を確認してください。このコードの実行結果を以下に示します。

# Subscribe

OnNext: 100

# Connect

OnNext: 1

OnNext: 2

OnNext: 3

OnCompleted

Connectメソッドを呼び出す前に OnNext: 100が表示されていることが確認できます。このよう

に、Publishメソッドの引数で渡した値が Subscribe直後に発行されていることがわかります。

4.14.4. PublishLast メソッド

ここでは、PublishLastメソッドについて説明します。PublishLastメソッドは最後に発行された

値を Connect後に流します。このメソッドのシグネチャを以下に示します。

public static IConnectableObservable<T> PublishLast<T>(this IObservable<T> source);

このメソッドの使用例を下記に示します。

// PublishLast

var source = Observable

// 1~3の値を発行する Coldな Observable

.Range(1, 3)

// PublishLastで Hot化

.PublishLast();

// 購読

Console.WriteLine("# Subscribe1");

source.Subscribe(

i => Console.WriteLine("Subscribe1#OnNext: {0}", i),

() => Console.WriteLine("Subscribe1#OnCompleted"));

// 接続

Console.WriteLine("# Connect");

source.Connect();

// 接続後にもう一度購読

Console.WriteLine("# Subscribe2");

Page 160: Reactive extensions入門v0.1

153

source.Subscribe(

i => Console.WriteLine("Subscribe2#OnNext: {0}", i),

() => Console.WriteLine("Subscribe2#OnCompleted"));

Connect前に1回購読(Subscribe1)をして、Connect後にもう一度購読(Subscribe2)しています。

このコードの実行結果を以下に示します。

# Subscribe1

# Connect

Subscribe1#OnNext: 3

Subscribe1#OnCompleted

# Subscribe2

Subscribe2#OnNext: 3

Subscribe2#OnCompleted

OnNextに、元になる IObservable<T>から発行される 1~3のうち最後の 3が表示されている

ことが確認できます。このことから、最後(OnCompleted直前)に発行された値のみ後続に流し

ていることがわかります。また、このメソッドは Connect後に購読しても最後の状態をキャッシ

ュしているため、Connect前に購読したものと同じ結果が表示されています。

4.14.5. Replay メソッド

ここでは Replayメソッドについて説明します。Replayメソッドは、購読した対象に今まで発行

した値を全て流すという特徴があります。このため、Connect後暫く放置して Subscribeをする

と一気に値が流れてきます。このメソッドのシグネチャを以下に示します。

public static IConnectableObservable<T> Replay<T>(this IObservable<T> source);

このメソッドの使用例を下記に示します。

// 500ms間隔で 0から値をカウントゕップしていく

var source = Observable

.Interval(TimeSpan.FromMilliseconds(500))

// Replayで Hotに変換

.Replay();

// 購読

Console.WriteLine("# Subscribe1");

using (

source.Subscribe(

i => Console.WriteLine("Subscribe1#OnNext: {0}", i)))

{

// 接続

Console.WriteLine("# Connect");

Page 161: Reactive extensions入門v0.1

154

source.Connect();

// 2秒待機

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

// 購読2

Console.WriteLine("# Subscribe2");

using (

source.Subscribe(

i => Console.WriteLine("Subscribe1#OnNext: {0}", i)))

{

// 1秒待機

Console.WriteLine("Sleep 1sec...");

Thread.Sleep(1000);

}

// 購読2解除

Console.WriteLine("# UnSubscribe2");

// 2秒待機

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

}

// 購読解除

Console.WriteLine("# UnSubscribe1");

Replayメソッドで作成した IConnectableObservable<T>に対して、2回 Subscribeを行ってい

ます。実行結果を以下に示します。

# Subscribe1

# Connect

Sleep 2sec...

Subscribe1#OnNext: 0

Subscribe1#OnNext: 1

Subscribe1#OnNext: 2

Subscribe1#OnNext: 3

# Subscribe2

Subscribe1#OnNext: 0

Subscribe1#OnNext: 1

Page 162: Reactive extensions入門v0.1

155

Subscribe1#OnNext: 2

Subscribe1#OnNext: 3

Sleep 1sec...

Subscribe1#OnNext: 4

Subscribe1#OnNext: 4

Subscribe1#OnNext: 5

Subscribe1#OnNext: 5

# UnSubscribe2

Sleep 2sec...

Subscribe1#OnNext: 6

Subscribe1#OnNext: 7

Subscribe1#OnNext: 8

Subscribe1#OnNext: 9

# UnSubscribe1

2回目の Subscribe(# Subscribe2が表示されている箇所)でどのように OnNextに値が渡ってい

るかに注目をしてください。Subscribeと同時に一気に 0~3の値が発行されていることが確認で

きます。このことから、Replayメソッドの挙動である、発行済みの値を購読した時点で一気に流

すということが確認できます。

4.14.6. Multicastメソッド

ここでは、Multicastメソッドについて説明します。Multicastメソッドは引数に応じて、Publish

メソッド、引数のある Publishメソッド、PublishLastメソッド、Replayメソッドと同じ動きを

する IConnectableObservable<T>を返すメソッドになります。このメソッドのシグネチャを以

下に示します。

public static IConnectableObservable<T> Multicast<T, U>(

this IObservable<T> source,

ISubject<T, U> subject);

引数の subjectに応じてPublish系の各メソッドと同じ動作をさせることが出来ます。ISubject<T,

U>ンターフェースはSubject<T>クラス等のSubject系クラスが実装しているンターフェー

スで Subject<T>クラスは ISubject<T, T>を実装しています。そのため、基本的に型引数の T

と Uは同じケースが現実問題としては多いので下記のようなメソッドの定義だと思っていて差支

え無いと思います。

public static IConnectableObservable<T> Multicast<T >(

this IObservable<T> source,

ISubject<T > subject);

このメソッドの使用例を下記に示します。

Console.WriteLine("# Multicast(new Subject<int>())");

var source = Observable

.Range(1, 3)

Page 163: Reactive extensions入門v0.1

156

// Publishと一緒

.Multicast(new Subject<int>());

// 購読

Console.WriteLine("# Subscribe");

source.Subscribe(

i => Console.WriteLine("OnNext: {0}", i),

() => Console.WriteLine("OnCompleted"));

// 接続

Console.WriteLine("# Connect");

source.Connect();

上記コードでは Multicastメソッドに Subject<T>クラスを渡しています。コメント内にもある通

り、これは引数の無い Publishメソッドと同じ動きになります。実行結果を以下に示します。

# Multicast(new Subject<int>())

# Subscribe

# Connect

OnNext: 1

OnNext: 2

OnNext: 3

OnCompleted

Publishメソッドと同様に Connect後に、値が発行されていることが確認できます。

次に、BehaviorSubject<T>を渡したコード例を下記に示します。

Console.WriteLine("# Multicast(new BehaviorSubject<int>(100))");

var source = Observable

.Range(1, 3)

// Publish(100)と一緒

.Multicast(new BehaviorSubject<int>(100));

// 購読

Console.WriteLine("# Subscribe");

source.Subscribe(

i => Console.WriteLine("OnNext: {0}", i),

() => Console.WriteLine("OnCompleted"));

// 接続

Page 164: Reactive extensions入門v0.1

157

Console.WriteLine("# Connect");

source.Connect();

BehaviorSubject<T>は、「5. Subject系クラス」で説明するクラスで、初期値を持つ Subject<T>

クラスになります。このコードの実行結果を以下に示します。

# Multicast(new BehaviorSubject<int>(100))

# Subscribe

OnNext: 100

# Connect

OnNext: 1

OnNext: 2

OnNext: 3

OnCompleted

引数のある Publishメソッドと同様に Subscribe直後に値が発行されて、Connect後に元になる

IObservable<T>のシーケンスの値が発行されていることが確認できます。

次に、AsyncSubject<T>を渡したコード例を下記に示します。

Console.WriteLine("# Multicast(new AsyncSubject<int>())");

var source = Observable

.Range(1, 3)

// PublishLastと一緒

.Multicast(new AsyncSubject<int>());

// 購読

Console.WriteLine("# Subscribe");

source.Subscribe(

i => Console.WriteLine("OnNext: {0}", i),

() => Console.WriteLine("OnCompleted"));

// 接続

Console.WriteLine("# Connect");

source.Connect();

AsyncSubject<T>クラスは、OnCompletedが発行される前の最後の値をキャッシュして購読者

に流す Subjectです。このコードの実行結果を以下に示します。

# Multicast(new AsyncSubject<int>())

# Subscribe

# Connect

OnNext: 3

Page 165: Reactive extensions入門v0.1

158

OnCompleted

上記の実行結果からもわかるとおり、最後の値だけが発行されています。このことから、

AsyncSubject<T>クラスを渡した場合は PublishLastと同様の動きを行います。

次に ReplaySubject<T>を渡したコード例を下記に示します。

Console.WriteLine("# Multicast(new ReplaySubject<long>())");

var source = Observable

.Interval(TimeSpan.FromMilliseconds(500))

// Replayと一緒

.Multicast(new ReplaySubject<long>());

// 接続

Console.WriteLine("# Connect");

source.Connect();

// 2秒待機

Console.WriteLine("Sleep 2sec...");

Thread.Sleep(2000);

// 購読

Console.WriteLine("# Subscribe");

source.Subscribe(

i => Console.WriteLine("OnNext: {0}", i),

() => Console.WriteLine("OnCompleted"));

ReplaySubject<T>クラスは、今まで発行した値を全てキャッシュしていて購読時に一気に発行

しなおす挙動をします。上記コードでは Connect後に 2秒待機して Intervalメソッドから 0~3

の 4つの値が発行された状態で Subscribeしています。実行結果を以下に示します。

# Multicast(new ReplaySubject<long>())

# Connect

Sleep 2sec...

# Subscribe

OnNext: 0

OnNext: 1

OnNext: 2

OnNext: 3

Subscribeをした時点で、これまで発行された値が一気に OnNextに渡っていることが確認でき

ます。

Page 166: Reactive extensions入門v0.1

159

この Multicastメソッドは、任意の Subjectを渡すことによって Coldな IObservable<T>から

Hotな IObservable<T>への変換を行います。その際の挙動を Subjectクラスを差し替えること

で様々な形にカスタマズできます。

5. Subject系クラス

ここでは、System.Reactive.Subjects名前空間に定義されている Subject<T>を代表する各種

Subject系のクラスについて説明します。

5.1. Subject<T>クラス

Subject<T>クラスは IObservable<T>と IObserver<T>の両方のンターフェースを実装する

クラスです。これまでのサンプルプログラム内で使ってきた通り、OnNext, OnError,

OnCompletedの各種メソッドを呼ぶことで、値の発行、エラーの発行、完了の通知を行います。

各種ある Subject系のクラスの中でも一番素直な挙動を行うクラスです。コード例下記に示しま

す。

var source = new Subject<int>();

// 購読 1

Console.WriteLine("# Subscribe1");

source.Subscribe(

i => Console.WriteLine("Subscribe1#OnNext: {0}", i),

ex => Console.WriteLine("Subscribe1#OnError: {0}", ex),

() => Console.WriteLine("Subscribe1#OnCompleted"));

// 購読 2

Console.WriteLine("# Subscribe2");

source.Subscribe(

i => Console.WriteLine("Subscribe2#OnNext: {0}", i),

ex => Console.WriteLine("Subscribe2#OnError: {0}", ex),

() => Console.WriteLine("Subscribe2#OnCompleted"));

// 値の発行~完了

Console.WriteLine("OnNext(1)");

source.OnNext(1);

Console.WriteLine("OnNext(2)");

source.OnNext(2);

Console.WriteLine("OnNext(3)");

source.OnNext(3);

Console.WriteLine("OnCompleted()");

Page 167: Reactive extensions入門v0.1

160

source.OnCompleted();

Subject<int>に対して 2回 Subscribeをした状態で値の発行から完了を行っています。実行結果

を以下に示します。

# Subscribe1

# Subscribe2

OnNext(1)

Subscribe1#OnNext: 1

Subscribe2#OnNext: 1

OnNext(2)

Subscribe1#OnNext: 2

Subscribe2#OnNext: 2

OnNext(3)

Subscribe1#OnNext: 3

Subscribe2#OnNext: 3

OnCompleted()

Subscribe1#OnCompleted

Subscribe2#OnCompleted

Subject<int>から値が発行される度に 2回 OnNextに値が渡っていることが確認できます。

Subject<T>クラスではエラーを通知するために、OnErrorで例外を発行することも出来ます。コ

ード例を下記に示します。

var source = new Subject<int>();

// 購読

Console.WriteLine("# Subscribe");

source.Subscribe(

i => Console.WriteLine("Subscribe1#OnNext: {0}", i),

ex => Console.WriteLine("Subscribe1#OnError: {0}", ex),

() => Console.WriteLine("Subscribe1#OnCompleted"));

// 例外

source.OnError(new Exception("Error!!"));

実行結果を以下に示します。

# Subscribe

Subscribe1#OnError: System.Exception: Error!!

Page 168: Reactive extensions入門v0.1

161

5.2. BehaviorSubject<T>クラス

BehaviorSubject<T>クラスは、初期値を持つ Subject<T>クラスになります。

BehaviorSubject<T>クラスのンスタンスを作成するときにコンストラクタの引数で初期値を

渡します。Subject<T>クラスでは Subscribeするだけでは値は発行されませんでしたが、

BehaviorSubject<T>クラスは Subscribeしたタミングで、初期値が発行されます。コード例

を下記に示します。

// 初期値 0の BehaviorSubject

var behaviorSubject = new BehaviorSubject<int>(0);

// 購読

Console.WriteLine("## Subscribe call");

behaviorSubject.Subscribe(

i => Console.WriteLine("OnNext({0})", i),

ex => Console.WriteLine("OnError({0})", ex.Message),

() => Console.WriteLine("OnCompleted()"));

// 順に値を発行して終了

Console.WriteLine("## OnNext(1) call");

behaviorSubject.OnNext(1);

Console.WriteLine("## OnNext(2) call");

behaviorSubject.OnNext(2);

Console.WriteLine("## OnNext(3) call");

behaviorSubject.OnNext(3);

Console.WriteLine("## OnCompleted() call");

behaviorSubject.OnCompleted();

上記コードの実行結果を下記に示します。

## Subscribe call

OnNext(0) <- ※Subscribeした時点で OnNextに通知がいってる

## OnNext(1) call

OnNext(1)

## OnNext(2) call

OnNext(2)

## OnNext(3) call

OnNext(3)

## OnCompleted() call

OnCompleted()

Page 169: Reactive extensions入門v0.1

162

Subscribeをした時点で、コンストラクタで渡した初期値の 0が通知されていることが確認でき

ます。また、BehaviorSubject<T>は、Subscribeしたタミングで最後の値を返すという特徴

もあります。この動作を確認するコードを下記に示します。

// 初期値 0の BehaviorSubject

var behaviorSubject = new BehaviorSubject<int>(0);

// 値を発行

Console.WriteLine("## OnNext(1) call");

behaviorSubject.OnNext(1);

Console.WriteLine("Subscribe");

// 値を発行した後に購読開始

behaviorSubject.Subscribe(

i => Console.WriteLine("OnNext({0})", i),

() => Console.WriteLine("OnCompleted()"));

behaviorSubject.OnCompleted();

// 終了した後に購読開始

Console.WriteLine("Subscribe");

behaviorSubject.Subscribe(

i => Console.WriteLine("OnNext({0})", i),

() => Console.WriteLine("OnCompleted()"));

実行結果を下記に示します。

## OnNext(1) call

Subscribe

OnNext(1)

OnCompleted()

Subscribe

OnCompleted()

OnNext(1)を呼び出した後に Subscribeすると 1が発行されていることが確認できます。また、

OnCompleted()をしたあとにSubscribeするとOnCompletedが呼ばれていることが確認できま

す。

5.3. AsyncSubject<T>クラス

ここでは、AsyncSubject<T>クラスについて説明します。AsyncSubject<T>クラスは名前の通

り非同期処理をラップする際によく使用します。このクラスの特徴は、OnNextを何回発行しても

Page 170: Reactive extensions入門v0.1

163

OnCompletedを行うまで値が発行されないという点です。そして OnCompletedが呼ばれると、

最後の OnNextの値と OnCompletedを Observerに通知します。また、OnCompletedが呼ばれ

た後に購読された場合にも、最後の OnNextの値と OnCompletedを Observerに通知します。

コード例を下記に示します。

var source = new AsyncSubject<int>();

// 購読

Console.WriteLine("# Subscribe1");

source.Subscribe(

i => Console.WriteLine("Subscribe1#OnNext: {0}", i),

ex => Console.WriteLine("Subscribe1#OnError: {0}", ex),

() => Console.WriteLine("Subscribe1#OnCompleted"));

// 値の発行~完了

Console.WriteLine("OnNext(1)");

source.OnNext(1);

Console.WriteLine("OnNext(2)");

source.OnNext(2);

Console.WriteLine("OnNext(3)");

source.OnNext(3);

Console.WriteLine("OnCompleted()");

source.OnCompleted();

// OnCompleted後の購読

Console.WriteLine("# Subscribe2");

source.Subscribe(

i => Console.WriteLine("Subscribe2#OnNext: {0}", i),

ex => Console.WriteLine("Subscribe2#OnError: {0}", ex),

() => Console.WriteLine("Subscribe2#OnCompleted"));

実行結果を以下に示します。

# Subscribe1

OnNext(1)

OnNext(2)

OnNext(3)

OnCompleted()

Subscribe1#OnNext: 3

Subscribe1#OnCompleted

Page 171: Reactive extensions入門v0.1

164

# Subscribe2

Subscribe2#OnNext: 3

Subscribe2#OnCompleted

OnCompletedメソッドが呼ばれたタミングでの値の発行と、OnCompletedメソッドが呼ばれ

た後の Subscribeでも値が発行されていることが確認できます。

5.4. ReplaySubject<T>クラス

ここでは、ReplaySubject<T>クラスについて説明します。ReplaySubject<T>クラスは、発行

した全ての値をキャッシュして Subscribeされたタミングでキャッシュしている値を全て発行

するという特徴があります。それ以外は、通常の Subject<T>クラスと同じ動きをします。コー

ド例を下記に示します。

var source = new ReplaySubject<int>();

// 購読

Console.WriteLine("# Subscribe1");

source.Subscribe(

i => Console.WriteLine("Subscribe1#OnNext: {0}", i),

ex => Console.WriteLine("Subscribe1#OnError: {0}", ex),

() => Console.WriteLine("Subscribe1#OnCompleted"));

// 値の発行~完了

Console.WriteLine("OnNext(1)");

source.OnNext(1);

Console.WriteLine("OnNext(2)");

source.OnNext(2);

Console.WriteLine("OnNext(3)");

source.OnNext(3);

Console.WriteLine("OnCompleted()");

source.OnCompleted();

// OnCompleted後の購読

Console.WriteLine("# Subscribe2");

source.Subscribe(

i => Console.WriteLine("Subscribe2#OnNext: {0}", i),

ex => Console.WriteLine("Subscribe2#OnError: {0}", ex),

() => Console.WriteLine("Subscribe2#OnCompleted"));

実行結果を以下に示します。

Page 172: Reactive extensions入門v0.1

165

# Subscribe1

OnNext(1)

Subscribe1#OnNext: 1

OnNext(2)

Subscribe1#OnNext: 2

OnNext(3)

Subscribe1#OnNext: 3

OnCompleted()

Subscribe1#OnCompleted

# Subscribe2

Subscribe2#OnNext: 1

Subscribe2#OnNext: 2

Subscribe2#OnNext: 3

Subscribe2#OnCompleted

最初の購読から値の発行、完了通知の流れでは Subject<T>クラスと同様の動きをしています。

完了後の購読時に、今まで発行した値と完了通知が名前の通りリプレされていることが確認でき

ます。

6. IObservable の合成

ここでは、IObservable<T>クラスの合成について説明します。いままで説明してきたメソッド

は単一の IObservable<T>のシーケンスに対して操作を行うものでした。それだけでも Throttle

や Skip, Take、Bufferメソッドや Distictメソッドなど Reactive Extensionsを使う上で重要か

つ特徴的なメソッドです。これから紹介するメソッドは、複数の IObservable<T>シーケンスを

合成して 1つの IObservable<T>のシーケンスを作成するメソッド群です。

Reactive Extensionsでは、時間、ベント、非同期などを IObservable<T>のシーケンスとし

て扱うことができます。つまり、時間、ベント、非同期などを合成して単一のシーケンスにした

あと、Distinctで重複を排除したり Bufferなどでまとめたり、Scanメソッドなどで集計をとった

りすることが出来ます。これは、Reactive Extensionsの特徴の 1つになります。

6.1. Merge メソッド

ここでは、Mergeメソッドについて説明します。Mergeメソッドは名前の通り複数の

IObservable<T>のシーケンスにするメソッドです。オーバーロードはいくつかありますが一番

シンプルな 2つの IObservable<T>のシーケンスを合成するメソッドのシグネチャを以下に示し

ます。

public static IObservable<T> Merge<T>(

this IObservable<T> first,

IObservable<T> second);

Page 173: Reactive extensions入門v0.1

166

このメソッドは、左辺値の IObservable<T>のシーケンスと右辺値の IObservable<T>のシーケ

ンスを 1つのシーケンスとしてマージします。コード例を下記に示します。

Console.WriteLine("# 2つの IObservable<T>の統合");

Observable

// 0~2の値を 1秒間隔で発行

.Interval(TimeSpan.FromSeconds(1)).Take(3)

// 1~3の値に変換

.Select(i => i + 1)

// マージ(統合)

.Merge(

Observable

// 0~2の値を 1秒間隔で発行

.Interval(TimeSpan.FromSeconds(1)).Take(3)

// 10, 20, 30に変換

.Select(i => (i + 1) * 10))

// 購読

.Subscribe(

i => Console.WriteLine("OnNext: {0}", i),

() => Console.WriteLine("OnCompleted"));

// Enter押すまで待機

Console.ReadLine();

1秒間隔で 1, 2, 3の値を発行する IObservable<T>のシーケンスと 1秒間隔で 10, 20, 30の値

を発行する IObservable<T>のシーケンスを Mergeメソッドで合成して購読しています。実行結

果を以下に示します。

# 2つの IObservable<T>の統合

OnNext: 1

OnNext: 10

OnNext: 2

OnNext: 20

OnNext: 3

OnNext: 30

OnCompleted

上記の結果からMergeメソッドでは leftと rightで渡した IObservable<T>のシーケンスから発

行された値を単純に後続に流していることが確認出来ます。

次に、複数の IObservable<T>のシーケンスを 1つに統合する Mergeメソッドのオーバーロード

についてみていきます。メソッドのシグネチャを以下に示します。

public static IObservable<T> Merge<T>(params IObservable<T>[] sources);

Page 174: Reactive extensions入門v0.1

167

引数の paramsで渡した IObservable<T>を全てマージします。コード例を下記に示します。

Console.WriteLine("# 複数の IObservable<T>の統合");

// Observable.Merge<T>(params IObservable<T>[] sources)

Observable.Merge(

// 3つの IObservable<T>をマージ

Observable.Range(0, 3),

Observable.Range(10, 3),

Observable.Range(100, 3))

// 購読

.Subscribe(

i => Console.WriteLine("OnNext: {0}", i),

() => Console.WriteLine("OnCompleted"));

// Enter押すまで待機

Console.ReadLine();

上記のコードは3つの IObservable<T>のシーケンスをMergeメソッドで合成して結果を出力し

ています。実行結果を以下に示します。

# 複数の IObservable<T>の統合

OnNext: 0

OnNext: 10

OnNext: 100

OnNext: 1

OnNext: 11

OnNext: 101

OnNext: 2

OnNext: 12

OnNext: 102

OnCompleted

3つの IObservable<T>のシーケンスが 1つに統合されていることが確認できます。

このメソッドで興味深いオーバーロードとして以下のようなものがあります。

public static IObservable<T> Merge<T>(this IObservable<IObservable<T>> sources);

IObservable<IObservable<T>>の拡張メソッドとして定義されています。このメソッドを使う

と、下記のように Subject<int>などのような型から IObservable<IObservable<T>>を作成し

て Mergeして 1つにつなげるということが出来るようになります。コード例を下記に示します。

Console.WriteLine("# 複数の IObservable<T>の統合 2");

// IObservable<T> Merge<T>(IObservable<IObservable<T>> source)

var source = new Subject<int>();

Page 175: Reactive extensions入門v0.1

168

source

// IObservable<int>から IObservable<IObservable<int>>に変換

.Select(i => Observable

// 0~2の値を 1秒間隔で 3つ発行

.Interval(TimeSpan.FromSeconds(1)).Take(3)

// 値を変換

.Select(l => (l + 1) * i))

// IObservable<IObservable<T>>から IObservable<T>へマージ

.Merge()

// 購読

.Subscribe(

i => Console.WriteLine("OnNext: {0}", i),

() => Console.WriteLine("OnCompleted"));

// 値の発行から完了

Console.WriteLine("# OnNext(10)");

source.OnNext(10);

Console.WriteLine("# OnNext(100)");

source.OnNext(100);

Console.WriteLine("# OnNext(1000)");

source.OnNext(1000);

Console.WriteLine("# OnCompleted");

source.OnCompleted();

// Enter押すまで待機

Console.ReadLine();

Subject<int>から発行された値を元に Intervalメソッドを使って 3つの値を発行する

IObservable<T>型を生成しています。これに対して Mergeメソッドを呼び出し

IObservable<IObservable<T>>を 1つの IObservable<T>にならしています。実行結果を以下

に示します。

# 複数の IObservable<T>の統合 2

# OnNext(10)

# OnNext(100)

# OnNext(1000)

# OnCompleted

OnNext: 10

OnNext: 100

Page 176: Reactive extensions入門v0.1

169

OnNext: 1000

OnNext: 200

OnNext: 20

OnNext: 2000

OnNext: 300

OnNext: 3000

OnNext: 30

OnCompleted

3つの IObservable<T>から発行された値が順不同(実行するごとに OnNext: ***の箇所の順番

が変わる)で表示されます。このように Mergeメソッドを使用することで、例えば、あるベン

トが発行されたタミングでWebAPIを非同期に実行して結果を受け取って処理を行うという処

理を書くことが出来ます。

6.2. SelectMany メソッド

ここでは SelectManyメソッドについて説明します。SelectManyメソッドには様々なオーバーロ

ードがありますが、一番使用頻度の高いオーバーロードは以下のものになります。

public static IObservable<R> SelectMany<T, R>(

this IObservable<T> source,

Func<T, IObservable<R>> selector);

sourceから発行された値を受け取って selectorで渡したデリゲート処理を行い

IObservable<R>を取得します。sourceから複数の値が発行されると複数の IObservable<R>

が内部で作成されますが、それらをすべて 1つの IObservable<R>にマージして後続に流します。

つまり、Mergeメソッドの最後のサンプルで示した IObservable<IObservable<T>>の引数を受

け取るオーバーロードと同様のことを内部でしています。コード例を下記に示します。

Console.WriteLine("# 複数の IObservable<T>の統合(SelectMany)");

// IObservable<T> Merge<T>(IObservable<IObservable<T>> source)

var source = new Subject<int>();

source

// 発行された値から IObservable<T>を作成してマージ(統合)する

.SelectMany(i => Observable

// 0~2の値を 1秒間隔で 3つ発行

.Interval(TimeSpan.FromSeconds(1)).Take(3)

// 値を変換

.Select(l => (l + 1) * i))

// 購読

.Subscribe(

i => Console.WriteLine("OnNext: {0}", i),

() => Console.WriteLine("OnCompleted"));

Page 177: Reactive extensions入門v0.1

170

// 値の発行から完了

Console.WriteLine("# OnNext(10)");

source.OnNext(10);

Console.WriteLine("# OnNext(100)");

source.OnNext(100);

Console.WriteLine("# OnNext(1000)");

source.OnNext(1000);

Console.WriteLine("# OnCompleted");

source.OnCompleted();

// Enter押すまで待機

Console.ReadLine();

IObservable<IObservable<T>>型の引数を受け取る Mergeメソッドのオーバーロードと同様

の処理を SelectManyメソッドで記述しています。実質 SelectManyは下記のように Selectメソ

ッドと Mergeメソッドを使って置き換え出来ます。

SelectMany(i => Observable.Return(i)) → Select(i => Observable.Return(i)).Merge()

実行結果を以下に示します。

# 複数の IObservable<T>の統合(SelectMany)

# OnNext(10)

# OnNext(100)

# OnNext(1000)

# OnCompleted

OnNext: 10

OnNext: 100

OnNext: 1000

OnNext: 20

OnNext: 200

OnNext: 2000

OnNext: 30

OnNext: 3000

OnNext: 300

OnCompleted

Mergeメソッドと同様の結果になっていることが確認できます。

ここで説明した SelectManyメソッドのオーバーラド以外に以下のようなオーバーラドがあ

ります。

Page 178: Reactive extensions入門v0.1

171

// IObservable<T>が IEnumerable<T>になった感じ

public static IObservable<R> SelectMany<T, R>(

this IObservable<T> source,

Func<T, IEnumerable<R>> selector);

// sourceから発行された値に関係なく IObservable<T>を作成する場合

public static IObservable<O> SelectMany<T, O>(

this IObservable<T> source,

IObservable<O> other);

// sourceから発行された値を元に IEnumerable<TCollection>を作り、sourceから発行された値と

// IEnumerable<TCollection>の値を元に結果を作成する。その結果を後続に流す。

public static IObservable<R> SelectMany<T, TCollection, R>(

this IObservable<T> source,

Func<T, IEnumerable<TCollection>> collectionSelector,

Func<T, TCollection, R> resultSelector);

// sourceから発行された値を元に IObservable<TCollection>を作り、sourceから発行された値と

// IObservable<TCollection>の値を元に結果を作成する。その結果を後続に流す。

public static IObservable<R> SelectMany<T, TCollection, R>(

this IObservable<T> source,

Func<T, IObservable<TCollection>> collectionSelector,

Func<T, TCollection, R> resultSelector);

// onNext, onError, onCompletedのそれぞれで IObservable<R>を作成して、マージして返す。

public static IObservable<R> SelectMany<T, R>(

this IObservable<T> source,

Func<T, IObservable<R>> onNext,

Func<Exception, IObservable<R>> onError,

Func<IObservable<R>> onCompleted);

全てのオーバーロードについてのコード例はここでは割愛します。ここでは上記のメソッドの 4

番目のコードの使用例を下記に示します。

Console.WriteLine("# 複数の IObservable<T>の統合(SelectManyのオーバーロード)");

var source = new Subject<int>();

source

.SelectMany(

// sourceから発行された値を元に i, i+1, i+2の値を発行する IObservable<int>を返す

i => Observable.Range(i, 3),

// sourceから発行された値と i => Observable.Range(i, 3)で生成された値を

// 使って最終的に発行される値を作成する。ここでは匿名型にしただけ。

(x, y) => new { x, y })

.Subscribe(Console.WriteLine);

// 値の発行から完了

Console.WriteLine("# OnNext(10)");

source.OnNext(10);

Page 179: Reactive extensions入門v0.1

172

Console.WriteLine("# OnNext(100)");

source.OnNext(100);

Console.WriteLine("# OnNext(1000)");

source.OnNext(1000);

Console.WriteLine("# OnCompleted");

source.OnCompleted();

// Enter押すまで待機

Console.ReadLine();

上記のコードの実行結果を以下に示します。

# 複数の IObservable<T>の統合(SelectManyのオーバーロード)

# OnNext(10)

{ x = 10, y = 10 }

{ x = 10, y = 11 }

{ x = 10, y = 12 }

# OnNext(100)

{ x = 100, y = 100 }

{ x = 100, y = 101 }

{ x = 100, y = 102 }

# OnNext(1000)

{ x = 1000, y = 1000 }

{ x = 1000, y = 1001 }

{ x = 1000, y = 1002 }

# OnCompleted

sourceから発行された値と、sourceから発行された値を元に作成した結果の

IObservable<TCollection>から発行される値を組み合わせるケースで使用します。

6.3. Switch メソッド

ここでは Switchメソッドについて説明します。Switchメソッドは、IObservable<T> Merge(self

IObservable<IObservable<T>> source)と同じシグネチャを持ちます。メソッドの定義を以下

に示します。

public static IObservable<T> Switch<T>(this IObservable<IObservable<T>> sources);

Mergeメソッドとの違いを確認するために、Mergeメソッドと Switchメソッドで同じ処理を書

いて比較します。まず、Mergeメソッドのコード例を下記に示します。

Console.WriteLine("# 複数の IObservable<T>の統合(Merge)");

// IObservable<T> Merge<T>(IObservable<IObservable<T>> source)

var source = new Subject<int>();

Page 180: Reactive extensions入門v0.1

173

source

.Select(i => Observable

// 1 * i, 2 * i, 3 * iの値を 1秒間隔で発行

.Interval(TimeSpan.FromSeconds(1)).Take(3)

// 値を変換

.Select(l => (l + 1) * i))

// 統合

.Merge()

.Subscribe(

i => Console.WriteLine("OnNext: {0}", i),

() => Console.WriteLine("OnCompleted"));

// 値の発行から完了

Console.WriteLine("# OnNext(10)");

source.OnNext(10);

Console.WriteLine("Sleep 2000ms...");

Thread.Sleep(2000);

Console.WriteLine("# OnNext(100)");

source.OnNext(100);

Console.WriteLine("Sleep 2000ms...");

Thread.Sleep(2000);

Console.WriteLine("# OnNext(1000)");

source.OnNext(1000);

Console.WriteLine("Sleep 2000ms...");

Thread.Sleep(2000);

Console.WriteLine("# OnCompleted");

source.OnCompleted();

Console.ReadLine();

若干複雑ですが、時間間隔をあけて複数の IObservable<T>のシーケンスが入り乱れて値を発行

するようにしています。実行結果を以下に示します。

# 複数の IObservable<T>の統合(Merge)

# OnNext(10)

Sleep 2000ms...

Page 181: Reactive extensions入門v0.1

174

OnNext: 10

OnNext: 20

# OnNext(100)

Sleep 2000ms...

OnNext: 30

OnNext: 100

# OnNext(1000)

Sleep 2000ms...

OnNext: 200

OnNext: 1000

OnNext: 300

# OnCompleted

OnNext: 2000

OnNext: 3000

OnCompleted

10, 100, 1000の 3つの値を発行して、そこからさらに 3つの値を発行する IObservable<T>の

シーケンスを作成しているので、合計で 9つの値が発行されます。このコードの Merge部分を

Switchメソッドに書き換えたコードを下記に示します。

Console.WriteLine("# 複数の IObservable<T>の統合(Switch)");

// IObservable<T> Merge<T>(IObservable<IObservable<T>> source)

var source = new Subject<int>();

source

.Select(i => Observable

// 1 * i, 2 * i, 3 * iの値を 1秒間隔で発行

.Interval(TimeSpan.FromSeconds(1)).Take(3)

.Select(l => (l + 1) * i))

// 最後

.Switch()

.Subscribe(

i => Console.WriteLine("OnNext: {0}", i),

() => Console.WriteLine("OnCompleted"));

// 値の発行から完了

Console.WriteLine("# OnNext(10)");

source.OnNext(10);

Console.WriteLine("Sleep 2000ms...");

Thread.Sleep(2000);

Page 182: Reactive extensions入門v0.1

175

Console.WriteLine("# OnNext(100)");

source.OnNext(100);

Console.WriteLine("Sleep 2000ms...");

Thread.Sleep(2000);

Console.WriteLine("# OnNext(1000)");

source.OnNext(1000);

Console.WriteLine("Sleep 2000ms...");

Thread.Sleep(2000);

Console.WriteLine("# OnCompleted");

source.OnCompleted();

Console.ReadLine();

このコードの実行結果を以下に示します。

# 複数の IObservable<T>の統合(Switch)

# OnNext(10)

Sleep 2000ms...

OnNext: 10

OnNext: 20

# OnNext(100)

Sleep 2000ms...

OnNext: 100

# OnNext(1000)

Sleep 2000ms...

OnNext: 1000

# OnCompleted

OnNext: 2000

OnNext: 3000

OnCompleted

上記の実行結果から確認できるように、Mergeの時と異なって 9個の値を発行しているにも関わ

らず、最終的に OnNextまで値が流れているのが 6つになっています。これは、Mergeメソッド

が元になる IObservable<T>が発行した値を全て後続に流すのに対して、Switchメソッドは、最

後に値を発行した IObservable<T>の値を後続に流します。Switchメソッドは、このような特徴

から一定間隔で非同期にデータを取得する処理を実行して、最後に応答が返ってきたところからデ

ータを抽出したいといったケースで利用できます。

Page 183: Reactive extensions入門v0.1

176

6.4. Concat メソッド

ここでは、Concatメソッドについて説明します。Concatメソッドは、その名前の通り複数の

IObservable<T>のシーケンスを直列で繋ぎます。例えば、3つの IObservable<T>のシーケン

スを渡すと 1つ目の IObservable<T>のシーケンスが完了するまでの間は 1つ目の

IObservable<T>のシーケンスを後続に流します。1つ目の IObservable<T>のシーケンスが完

了すると、2つ目の IObservable<T>のシーケンスの値を流します。2つ目の IObservable<T>

のシーケンスが完了すると、3つ目の IObservable<T>のシーケンスの値を流します。そして 3

つ目の IObservable<T>のシーケンスが完了すると、完了したということを後続に通知します。

Concatメソッドには 2つのシーケンスを繋ぐことに特化したオーバーロードと、複数のシーケン

スを繋ぐためのメソッドのオーバーロードがあります。このメソッドのオーバーロードを以下に示

します。

// 引数で渡した全ての IObservable<T>のシーケンスを繋ぐ

public static IObservable<T> Concat<T>(params IObservable<T>[] sources);

// IEnumerable<IObservable<T>>から取得できるすべての IObservable<T>を繋ぐ

public static IObservable<T> Concat<T>(this IEnumerable<IObservable<T>> sources);

// IObservable<IObservable<T>>から発行されるすべての IObservable<T>を繋ぐ

public static IObservable<T> Concat<T>(this IObservable<IObservable<T>> sources);

// firstと secondを繋ぐ

public static IObservable<T> Concat<T>(this IObservable<T> first, IObservable<T> second);

Concatメソッドの動作を示すためのコード例を下記に示します。

Observable

// IObservable<T>のシーケンスを直列につなげる

.Concat(

// "1st: 0" ~ "1st: 2"までの値を 1秒間隔で発行する

Observable.Interval(TimeSpan.FromSeconds(1)).Select(i => "1st: " + i).Take(3),

// "2nd: 0" ~ "2nd: 2"までの値を 1秒間隔で発行する

Observable.Interval(TimeSpan.FromSeconds(1)).Select(i => "2nd: " + i).Take(3),

// "3rd: 0" ~ "3rd: 2"までの値を 1秒間隔で発行する

Observable.Interval(TimeSpan.FromSeconds(1)).Select(i => "3rd: " + i).Take(3))

// 購読

.Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

() => Console.WriteLine("OnCompleted"));

Console.ReadLine();

Observable.Intervalメソッドを使って 1秒間隔で値を発行する IObservable<T>のシーケンス

を 3つ作成して、Concatメソッドで繋いでいます。1秒間隔で値を発行するため、Mergeメソッ

Page 184: Reactive extensions入門v0.1

177

ドでは 1つ目と 2つ目と 3つ目の IObservable<T>のシーケンスの発行する値が混在しますが、

Concatは前の IObservable<T>のシーケンスが完了しない限り後ろの IObservable<T>のシー

ケンスが発行する値を、後続に流さないため上記のサンプルでは Subscribeの OnNextに値が渡

る順番が保障されています。実行結果を以下に示します。

OnNext: 1st: 0

OnNext: 1st: 1

OnNext: 1st: 2

OnNext: 2nd: 0

OnNext: 2nd: 1

OnNext: 2nd: 2

OnNext: 3rd: 0

OnNext: 3rd: 1

OnNext: 3rd: 2

OnCompleted

前の IObservable<T>のシーケンスが完了するまで次の IObservable<T>のシーケンスが発行さ

れる値が OnNextに流れていないことが確認できます。このプログラムは、可変長引数のオーバ

ーロードを使いましたが、下記のように別のオーバーロードを使っても同じように書くことができ

ます。

// "1st: 0" ~ "1st: 2"までの値を 1秒間隔で発行する

Observable.Interval(TimeSpan.FromSeconds(1))

.Select(i => "1st: " + i)

.Take(3)

.Concat(

// "2nd: 0" ~ "2nd: 2"までの値を 1秒間隔で発行する

Observable.Interval(TimeSpan.FromSeconds(1))

.Select(i => "2nd: " + i)

.Take(3))

.Concat(

// "3rd: 0" ~ "3rd: 2"までの値を 1秒間隔で発行する

Observable.Interval(TimeSpan.FromSeconds(1))

.Select(i => "3rd: " + i)

.Take(3))

// 購読

.Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

() => Console.WriteLine("OnCompleted"));

Page 185: Reactive extensions入門v0.1

178

Console.ReadLine();

実行結果は同じため省略します。

6.5. Zip メソッド

ここでは、Zipメソッドについて説明します。Zipメソッドは、2つの IObservable<T>のシーケ

ンスから値が発行されるのを待って、2つの値を変換して、変換した結果を後ろに流すメソッドで

す。メソッドのシグネチャを下記に示します。

public static IObservable<R> Zip<T, U, R>(

this IObservable<T> first,

IObservable<U> second,

Func<T, U, R> resultSelector);

firstから発行された値と secondから発行された値を resultSelectorで変換して後ろに流します。

また、firstか secondのどちらかの IObservable<T>のシーケンスが完了状態になった段階で後

続に完了通知を発行します。このメソッドのコード例を下記に示します。

// 1秒間隔で 0から値をカウントゕップしていく IObservable<T>のシーケンスの最初の 3つ

Observable.Interval(TimeSpan.FromSeconds(1)).Take(3)

// Zipでまとめる

.Zip(

// 100, 101, 102を発行する IObservable<T>のシーケンス

Observable.Range(100, 3),

// 渡された値を元に文字列化

(l, r) => string.Format("{0}-{1}", l, r))

// 購読

.Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

() => Console.WriteLine("OnCompleted"));

Console.ReadLine();

1秒間隔で 0, 1, 2の値を発行する IObservable<T>のシーケンスと 100, 101, 102の値を発行

する IObservable<T>のシーケンスを Zipメソッドで合成しています。実行結果を以下に示しま

す。

OnNext: 0-100

OnNext: 1-101

OnNext: 2-102

OnCompleted

Zipメソッドで合成された 3つの値が OnNextに渡ってきていることが確認できます。上記のプロ

グラムを少し変更して片方の IObservable<T>のシーケンスが発行する値の数を 1つにしたコー

ドを下記に示します。

Page 186: Reactive extensions入門v0.1

179

// 1秒間隔で 0から値をカウントゕップしていく IObservable<T>のシーケンスの最初の 3つ

Observable.Interval(TimeSpan.FromSeconds(1)).Take(3)

// Zipでまとめる

.Zip(

// 100を発行する IObservable<T>のシーケンス

Observable.Return(100),

// 渡された値を元に文字列化

(l, r) => string.Format("{0}-{1}", l, r))

// 購読

.Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

() => Console.WriteLine("OnCompleted"));

Console.ReadLine();

Intervalメソッドから Take(3)で 3つの値を発行する IObservable<T>のシーケンスと Return

メソッドで 1つの値しか発行しない IObservable<T>のシーケンスを Zipメソッドで合成してい

ます。実行結果を下記に示します。

OnNext: 0-100

OnCompleted

OnNextが 1回しか実行されていないことが確認できます。このことから Zipメソッドが、合成対

象の IObservable<T>のシーケンスのどちらか片方が終了した段階で、後続に完了通知を発行す

ることが確認できます。

6.6. Amb メソッド

ここでは Ambメソッドについて説明します。Ambメソッドは複数の IObservable<T>のシーケ

ンスの中から一番最初に値を発行した IObservable<T>のシーケンスの値を後続に流すメソッド

です。このメソッドのオーバーロードを以下に示します。

// 引数で渡した全ての IObservable<T>から最初に値を発行した IObservable<T>の値を後ろに流す

public static IObservable<T> Amb<T>(params IObservable<T>[] sources);

// sources内の全ての IObservable<T>から最初に値を発行した IObservable<T>の値を後ろに流す

public static IObservable<T> Amb<T>(this IEnumerable<IObservable<T>> sources);

// firstと secondのうち最初に値を発行したものの値を後ろに流す

public static IObservable<T> Amb<T>(this IObservable<T> first, IObservable<T> second);

他の合成系メソッドと同様に可変長引数や IEnumerable<IObservable<T>>の拡張メソッドや

2つの合成に特化したオーバーロードが用意されています。Ambメソッドのコード例を下記に示

します。

Observable.Amb(

Page 187: Reactive extensions入門v0.1

180

// 3秒後に値を発行する IO<T>

Observable.Timer(TimeSpan.FromSeconds(3)).Select(_ => "3sec"),

// 10秒後に値を発行する IO<T>

Observable.Timer(TimeSpan.FromSeconds(10)).Select(_ => "10sec"),

// 2秒後に値を発行する IO<T>

Observable.Timer(TimeSpan.FromSeconds(2)).Select(_ => "2sec"),

// 6秒後に値を発行する IO<T>

Observable.Timer(TimeSpan.FromSeconds(6)).Select(_ => "6sec"),

// 22秒後に値を発行する IO<T>

Observable.Timer(TimeSpan.FromSeconds(22)).Select(_ => "22sec"))

.Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

() => Console.WriteLine("OnCompleted"));

Timerメソッドを使って、指定した時間が経過した後に、値を発行する IObservable<T>のシー

ケンスを Ambメソッドで合成しています。実行結果を以下に示します。

OnNext: 2sec

OnCompleted

引数で渡した中で一番早く値を発行する

Observable.Timer(TimeSpan.FromSecond(2)).Select(_ => “2sec”)の結果が OnNextや

OnCompletedに渡っていることが確認できます。

6.7. CombineLatest メソッド

ここでは、CombineLatestメソッドについて説明します。CombineLatestメソッドは Zipメソッ

ドのように 2つの IObservable<T>のシーケンスを合成するメソッドです。Zipメソッドが両方

の IObservable<T>のシーケンスから値が発行されるのを待つのに対して CombineLatestメソ

ッドは、どちらか一方から値が発行されると、もう一方の最後に発行した値があるか確認し、あれ

ばそれを使って合成処理を行い、後続に値を流します。このメソッドのシグネチャを以下に示しま

す。

public static IObservable<R> CombineLatest<F, S, R>(

this IObservable<F> first,

IObservable<S> second,

Func<F, S, R> resultSelector);

このメソッドのコード例を下記に示します。

var source1 = new Subject<string>();

var source2 = new Subject<int>();

source1

// source1と source2を合成

Page 188: Reactive extensions入門v0.1

181

.CombineLatest(

source2,

// 発行された値を結合して文字列化

(l, r) => string.Format("{0}-{1}", l, r))

// 購読

.Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

() => Console.WriteLine("OnCompleted"));

// 適当に値を発行する

Console.WriteLine("source1.OnNext(foo)");

source1.OnNext("foo");

Console.WriteLine("source2.OnNext(100)");

source2.OnNext(100);

Console.WriteLine("source2.OnNext(200)");

source2.OnNext(200);

Console.WriteLine("source1.OnNext(bar)");

source1.OnNext("bar");

Console.WriteLine("source1.OnNext(boo)");

source1.OnNext("boo");

// source1完了

Console.WriteLine("source1.OnCompleted()");

source1.OnCompleted();

// source1完了後に source2から値を発行する

Console.WriteLine("source2.OnNext(200)");

source2.OnNext(999);

// source2完了

Console.WriteLine("source2.OnCompleted()");

source2.OnCompleted();

このコードの実行結果を以下に示します。

source1.OnNext(foo)

source2.OnNext(100)

OnNext: foo-100

source2.OnNext(200)

OnNext: foo-200

source1.OnNext(bar)

Page 189: Reactive extensions入門v0.1

182

OnNext: bar-200

source1.OnNext(boo)

OnNext: boo-200

source1.OnCompleted()

source2.OnNext(200)

OnNext: boo-999

source2.OnCompleted()

OnCompleted

最初の OnNextでは、source1と source2の値が両方発行されるまで実行されませんが、それ以

降は、source1か source2のどちらかから値が発行されると OnNextに値が発行されていること

が確認できます。このことから、CombineLatestメソッドの特徴である、最後に発行された値を

キャッシュしているということがわかります。そして、CombineLatestメソッドは両方のシーケ

ンスが完了状態になった時点で完了通知を行うという動作も確認できます。これは、片方が完了し

ても、もう一方から値が発行されたら最後に発行された値を元に処理を継続できるためです。

6.8. StartWith メソッド

ここでは、StartWithメソッドについて説明します。StartWithメソッドのシグネチャを以下に示

します。

public static IObservable<T> StartWith<T>(this IObservable<T> source, params T[] values);

このメソッドは、sourceで渡した IObservable<T>のシーケンスの先頭に valuesで指定した要

素を合成して 1つの IObservable<T>のシーケンスにします。このメソッドの使用例を下記に示

します。

Observable

// 1~3の値を発行する IObservable<T>のシーケンス

.Range(1, 3)

// 頭に 10, 20, 30をつける

.StartWith(10, 20, 30)

// 購読

.Subscribe(

i => Console.WriteLine("OnNext: {0}", i));

Rangeメソッドを使って 1~3の値を発行する IObservable<T>のシーケンスを作成して、

StartWithメソッドで 10, 20, 30の値を先頭に追加して、Subscribeメソッドで購読をしていま

す。このメソッドの実行例を以下に示します。

OnNext: 10

OnNext: 20

OnNext: 30

OnNext: 1

Page 190: Reactive extensions入門v0.1

183

OnNext: 2

OnNext: 3

6.9. Joinメソッド

ここでは、Joinメソッドについて説明します。Joinメソッドは 2つの IObservable<T>のシーケ

ンスから発行された値の全ての組み合わせをもとに、値を生成して発行する IObservable<T>の

シーケンスを作成します。このメソッドのシグネチャを以下に示します。

public static IObservable<TResult> Join<TLeft, TRight, TLeftDuration, TRightDuration, TResult>(

this IObservable<TLeft> left,

IObservable<TRight> right,

Func<TLeft, IObservable<TLeftDuration>> leftDurationSelector,

Func<TRight, IObservable<TRightDuration>> rightDurationSelector,

Func<TLeft, TRight, TResult> resultSelector);

Joinメソッドは、leftと rightから値が発行されると、それぞれ leftDurationSelector引数と

rightDurationSelector引数で指定したデリゲートが呼ばれて、その値の有効期間を示す

IObservable<T>が生成されます。この有効期間を示す IObservable<T>から値が発行されたら

leftと rightから発行された対象となる値は Joinメソッドで使われなくなります。そして、leftと

rightから値が発行されると、現在有効な値の全てを使って resultSelectorが呼び出されます。例

えば leftから発行された値で有効なものが(1, 2, 3)で rightから発行された値で有効なものが(10,

20, 30)の場合は、resultSelectorは(1, 10)(1, 20)(1, 30)(2, 10)(2, 20)(2, 30)(3, 10)(3,

20)(3, 30)の組み合わせの引数で呼び出されます。ここで生成された値が Joinメソッドの結果の

IObservable<TResult>から発行される値になります。

このメソッドのコード例を下記に示します。

// Joinで合成する IObservable<T>

var left = new Subject<int>();

var right = new Subject<int>();

left.Join(

right,

// leftから発行される値の有効期間は永久

_ => Observable.Never<Unit>(),

// rightから発行される値の有効期間は永久

_ => Observable.Never<Unit>(),

// 発行された値の組を作る

Tuple.Create)

// 購読

.Subscribe(

// 組を表示

tuple => Console.WriteLine("Left: {0}, Right: {1}", tuple.Item1, tuple.Item2),

Page 191: Reactive extensions入門v0.1

184

// 完了を表示

() => Console.WriteLine("OnCompleted"));

// 値の発行

Console.WriteLine("left.OnNext(1)");

left.OnNext(1);

Console.WriteLine("right.OnNext(10)");

right.OnNext(10);

Console.WriteLine("right.OnNext(100)");

right.OnNext(100);

Console.WriteLine("left.OnNext(2)");

left.OnNext(2);

// 終了

Console.WriteLine("left.OnCompleted()");

left.OnCompleted();

Console.WriteLine("right.OnCompleted()");

right.OnCompleted();

Joinメソッドを使って 2つの IObservable<T>のシーケンスから発行される値を組(Tuple)にし

ています。Joinの引数で渡した IObservable<T>のシーケンスから発行される値の有効期間は、

Observable.Neverメソッドを使って永遠に OnNextの呼ばれない IObservable<T>のシーケン

スを指定しているため、永遠に無効にならないようにしています。Subscribeでは組の値を表示し

ています。このコードの実行結果を以下に示します。

left.OnNext(1) ← この段階では leftに 1があるだけ

right.OnNext(10) ← この段階で leftに 1, rightに 10があるため(1, 10)の組が生成される

Left: 1, Right: 10

right.OnNext(100) ← この段階で leftに 1, rightに 10, 100があるため (1, 100)の組が追加で生成される

Left: 1, Right: 100

left.OnNext(2) ← この段階で leftに 1, 2, rightに 10, 100があるため(2, 10), (2, 100)の組が追加で生成される

Left: 2, Right: 10

Left: 2, Right: 100

left.OnCompleted()

right.OnCompleted()

OnCompleted ← rightと leftが両方終了したため、これ以降新たな組み合わせが出来ないため終了

この例のように値の有効期間に Observable.Neverを使うと、leftと rightから発行された全ての

組合わせを生成することが出来ます。Never以外の値の有効期間を指定したときのコード例を下

記に示します。

Page 192: Reactive extensions入門v0.1

185

// Joinで合成する IObservable<T>

var left = new Subject<int>();

var right = new Subject<int>();

left.Join(

right,

// leftから発行される値の有効期間は永久

_ => Observable.Never<Unit>(),

// rightから発行される値の有効期間は一瞬

_ => Observable.Empty<Unit>(),

// 発行された値の組を作る

Tuple.Create)

// 購読

.Subscribe(

// 組を表示

tuple => Console.WriteLine("Left: {0}, Right: {1}", tuple.Item1, tuple.Item2),

// 完了を表示

() => Console.WriteLine("OnCompleted"));

// 値の発行

Console.WriteLine("left.OnNext(1)");

left.OnNext(1);

Console.WriteLine("right.OnNext(10)");

right.OnNext(10);

Console.WriteLine("right.OnNext(100)");

right.OnNext(100);

Console.WriteLine("left.OnNext(2)");

left.OnNext(2);

Console.WriteLine("right.OnNext(1000)");

right.OnNext(1000);

// 終了

Console.WriteLine("left.OnCompleted()");

left.OnCompleted();

Console.WriteLine("right.OnCompleted()");

right.OnCompleted();

Page 193: Reactive extensions入門v0.1

186

上記のコードは rightから発行された値の有効期間を Observable.Empty<Unit>()で指定してい

るため、値が発行された瞬間しか有効期間が無いようにしています。このプログラムの実行結果を

以下に示します。

left.OnNext(1)

right.OnNext(10)

Left: 1, Right: 10

right.OnNext(100)

Left: 1, Right: 100

left.OnNext(2)

right.OnNext(1000)

Left: 1, Right: 1000

Left: 2, Right: 1000

left.OnCompleted()

right.OnCompleted()

OnCompleted

rightから値が発行されると、今まで leftから発行された値との組み合わせが生成されていること

が確認できます。逆に leftから値が発行されても rightから発行された値には有効なものが 1つも

ないため、resultSelectorで指定したデリゲートは呼ばれません。

ここでは、コード例を示しませんが、値の有効期限にタマーやベントなどから生成した

IObservable<T>を指定することで、よりきめ細やかに値の有効期限を指定することが出来ます。

6.10. GroupJoinメソッド

ここでは、GroupJoinメソッドについて説明します。GroupJoinメソッドは、2つの

IObservable<T>のシーケンスのうち左辺の値に対して右辺の値の IObservable<T>のシーケン

スの組み合わせを合成します。このメソッドのシグネチャを以下に示します。

public static IObservable<TResult> GroupJoin<TLeft, TRight, TLeftDuration, TRightDuration, TResult>(

this IObservable<TLeft> left,

IObservable<TRight> right,

Func<TLeft, IObservable<TLeftDuration>> leftDurationSelector,

Func<TRight, IObservable<TRightDuration>> rightDurationSelector,

Func<TLeft, IObservable<TRight>, TResult> resultSelector);

Joinメソッドと、ほぼ同じシグネチャですが、resultSelectorデリゲートの引数に違いがありま

す。resultSelectorデリゲートの第二引数が Joinメソッドでは TRight型だったのに対して

GroupJoinメソッドでは IObservable<TRight>になります。GroupJoinメソッドの

resultSelectorは、left引数から値が発行されたタミングで呼び出されます。この時の

resultSelectorの第一引数が、leftから発行される値で、第二引数が rightから発行された有効期

限内の値を発行する IObservable<TRight>になります。このメソッドの使用例を下記に示します。

// センサー名

var sensors = new Subject<string>();

Page 194: Reactive extensions入門v0.1

187

// センサーが受信する値

var values = new Subject<int>();

// 値のリセット用 Subject

var valueReset = new Subject<Unit>();

sensors.GroupJoin(

values,

// センサーは有効期限無し

_ => Observable.Never<Unit>(),

// センサーの値は valueResetの OnNextで無効に出来る

_ => valueReset,

// センサーの名前と、センサーが受け取った値の現在の合計値を発行する Logにして後続に流す

(l, r) => new { Name = l, Log = r.Scan((x, y) => x + y) })

.Subscribe(

sensor =>

{

// Logを表示する

sensor

.Log

.Subscribe(i => Console.WriteLine("{0}: {1}", sensor.Name, i));

},

// 完了

() => Console.WriteLine("OnCompleted"));

// センサーを 2つ登録

Console.WriteLine("sensors.OnNext(sensor1)");

sensors.OnNext("sensor1");

Console.WriteLine("sensors.OnNext(sensor2)");

sensors.OnNext("sensor2");

// 値を 3つ発行

Console.WriteLine("values.OnNext(100)");

values.OnNext(100);

Console.WriteLine("values.OnNext(10)");

values.OnNext(10);

Console.WriteLine("values.OnNext(1)");

Page 195: Reactive extensions入門v0.1

188

values.OnNext(1);

// センサーの値を一旦リセット

Console.WriteLine("valueReset.OnNext()");

valueReset.OnNext(Unit.Default);

// 新しいセンサーを追加

Console.WriteLine("sensors.OnNext(sensor3)");

sensors.OnNext("sensor3");

// 値を 3つ発行

Console.WriteLine("values.OnNext(1)");

values.OnNext(1);

Console.WriteLine("values.OnNext(2)");

values.OnNext(2);

Console.WriteLine("values.OnNext(3)");

values.OnNext(3);

// 終了

Console.WriteLine("values.OnCompleted()");

values.OnCompleted();

Console.WriteLine("sensors.OnCompleted()");

sensors.OnCompleted();

コードのメージはセンサーと、センサーが受信した値の合計値をリゕルタムで表示するプログ

ラムです。センサーが受信する値は任意のタミング(valueReset変数の OnNext)でリセットで

きるようにしています。このメソッドの実行結果を以下に示します。

sensors.OnNext(sensor1)

sensors.OnNext(sensor2)

values.OnNext(100)

sensor1: 100

sensor2: 100

values.OnNext(10)

sensor1: 110

sensor2: 110

values.OnNext(1)

sensor1: 111

sensor2: 111

valueReset.OnNext()

Page 196: Reactive extensions入門v0.1

189

sensors.OnNext(sensor3)

values.OnNext(1)

sensor1: 112

sensor2: 112

sensor3: 1

values.OnNext(2)

sensor1: 114

sensor2: 114

sensor3: 3

values.OnNext(3)

sensor1: 117

sensor2: 117

sensor3: 6

values.OnCompleted()

sensors.OnCompleted()

OnCompleted

sensor1と sensor2は、発行された値の合計を全て保持していることが確認できますが、

valueResetの OnNextを呼んだ後に追加した sensor3は、途中からの値の合計しか集計していな

いことが確認できます。

6.11. When メソッド

ここではWhenメソッドについて説明します。Whenメソッドのシグネチャを以下に示します。

public static IObservable<TResult> When<TResult>(params Plan<TResult>[] plans);

public static IObservable<TResult> When<TResult>(this IEnumerable<Plan<TResult>> plans);

Whenメソッドには 2つのオーバーロードがあり、片方が Plan<TResult>型の可変長引数で、も

う一方が IEnumerable<Plan<TResult>>の拡張メソッドです。どちらも複数の Plan<TResult>

を纏めるという意味合いになります。

6.11.1. Plan<TResult>クラスの作成

Whenメソッドの引数に渡す Plan<TResult>クラスの作成方法について説明します。

Plan<TResult>クラスのンスタンスを作成する前に、まず Patternというクラスのンスタン

スを用意します。Patternクラスは Andというメソッドで作成します。Andメソッドは

IObservable<T>のシーケンスを引数に取る拡張メソッドとして定義されています。シグネチャ

を以下に示します。

public static Pattern<TLeft, TRight> And<TLeft, TRight>(

this IObservable<TLeft> left,

IObservable<TRight> right);

Page 197: Reactive extensions入門v0.1

190

この戻り値の Pattern<TLeft, TRight>クラスには IObservable<T>を受け取る Andというメソ

ッドが定義されています。シグネチャを以下に示します。

public Pattern<T1, T2, T3> And<T3>(IObservable<T3> other);

このように、この Pattern<T1, T2, T3>にも IObservable<T>を受け取って Pattern<T1, T2, T3,

T4>型の値を返すメソッドが定義されています。このように、Andメソッドを使って複数の

IObservable<T>のシーケンスを繋げて Patternクラスにまとめることが出来ます。Patternクラ

スの型引数は、16個まで定義されているので事実上数を気にすることなくつなげていくことが出

来ます。

Patternクラスには型引数の数の引数を受け取るデリゲートを受け取る Thenメソッドが定義され

ています。Pattern<T1, T2, T3>クラスの場合の Thenメソッドのシグネチャを以下に示します。

public Plan<TResult> Then<TResult>(Func<T1, T2, T3, TResult> selector);

このメソッドから Plan<TResult>クラスのンスタンスが作成されます。Thenメソッドで渡し

たデリゲートは、それまでの Andで繋いだ IObservable<T>のシーケンスの全てから値が発行さ

れたタミングで実行されます。

6.11.2. Whenメソッドの使用例

では、上記の Andメソッドと Thenメソッドを使って Plan<TResult>クラスのンスタンスを作

成するコード例を下記に示します。

var plan = Observable

// plan1という文字列を 10個発行する IObservable<string>

.Return("plan1").Repeat(10)

// 1秒間隔で 0からのカウントゕップにタムスタンプをつける IObservable<Timestamped<long>>

.And(Observable.Interval(TimeSpan.FromSeconds(1)).Timestamp())

// 100~110の値を発行する IObservable<int>

.And(Observable.Range(100, 10))

// 3つの値を文字列として纏める

.Then((planName, timestamped, value) =>

string.Format("{0} {1} {2}", planName, timestamped, value));

// Whenで Plan<string>から IObservable<string>にして購読

Observable.When(plan).Subscribe(

s => Console.WriteLine("OnNext: {0}", s),

() => Console.WriteLine("OnCompleted"));

Console.ReadLine();

コメントにある通りですが、無限に値を発行し続ける IObservable<T>のシーケンスから 10個

の値を発行する IObservable<T>のシーケンスを Andで繋いで Thenメソッドで文字列化してい

ます。そして、Whenメソッドに渡して購読しています。Thenメソッドは、全ての Andメソッ

ドから値が発行されたタミングで実行されるため、もっとも短い 10個しか値を発行しない

IObservable<T>のシーケンスが終了したタミングで Thenメソッドも呼ばれなくなります。そ

Page 198: Reactive extensions入門v0.1

191

のため、この Plan<string>から生成した IObservable<string>のシーケンスは 10個しか値を発

行しないということになります。実行結果を以下に示します。

OnNext: plan1 0@2012/02/22 23:47:09 +09:00 100

OnNext: plan1 1@2012/02/22 23:47:10 +09:00 101

OnNext: plan1 2@2012/02/22 23:47:11 +09:00 102

OnNext: plan1 3@2012/02/22 23:47:12 +09:00 103

OnNext: plan1 4@2012/02/22 23:47:13 +09:00 104

OnNext: plan1 5@2012/02/22 23:47:14 +09:00 105

OnNext: plan1 6@2012/02/22 23:47:15 +09:00 106

OnNext: plan1 7@2012/02/22 23:47:16 +09:00 107

OnNext: plan1 8@2012/02/22 23:47:17 +09:00 108

OnNext: plan1 9@2012/02/22 23:47:18 +09:00 109

OnCompleted

Andで連結した IObservable<T>のシーケンスから発行された値を Thenで加工した結果が表示

されていることが確認できます。また、10個の値が発行されたら OnCompletedが呼ばれている

ことも確認できます。

次に、複数の Plan<TResult>をWhenメソッドに渡した場合のコード例を下記に示します。

var plan1 = Observable

// plan1という文字列を 10個発行する IObservable<string>

.Return("plan1").Repeat(10)

// 1秒間隔で 0からのカウントゕップにタムスタンプをつける IObservable<Timestamped<long>>

.And(Observable.Interval(TimeSpan.FromSeconds(1)).Timestamp())

// 100~110の値を発行する IObservable<int>

.And(Observable.Range(100, 10))

// 3つの値を文字列として纏める

.Then((planName, timestamped, value) =>

string.Format("{0} {1} {2}", planName, timestamped, value));

var plan2 = Observable

// plan2という文字列を 20個発行する IObservable<string>

.Return("plan2").Repeat(20)

// 0.5s間隔で 0から値を発行していく IObservable<long>

.And(Observable.Interval(TimeSpan.FromSeconds(0.5)))

// Thenで文字列に纏める

.Then((s, l) => string.Format("{0} {1}", s, l));

Observable.When(plan1, plan2).Subscribe(

Page 199: Reactive extensions入門v0.1

192

s => Console.WriteLine("OnNext: {0}", s),

() => Console.WriteLine("OnCompleted"));

plan1は、最初の例と同じように作成しています。plan2は、20個の plan2という文字列と、0.5

秒間隔で 0から値をカウントゕップしていく IObservable<long>を Andで繋いで Then内で文

字列化しています。この2つのWhenで繋いで IObservable<string>に変換して購読しています。

実行結果を以下に示します。

OnNext: plan2 0

OnNext: plan1 0@2012/02/22 23:53:21 +09:00 100

OnNext: plan2 1

OnNext: plan2 2

OnNext: plan2 3

OnNext: plan1 1@2012/02/22 23:53:22 +09:00 101

OnNext: plan2 4

OnNext: plan2 5

OnNext: plan1 2@2012/02/22 23:53:23 +09:00 102

OnNext: plan2 6

OnNext: plan2 7

OnNext: plan1 3@2012/02/22 23:53:24 +09:00 103

OnNext: plan2 8

OnNext: plan2 9

OnNext: plan1 4@2012/02/22 23:53:25 +09:00 104

OnNext: plan2 10

OnNext: plan2 11

OnNext: plan1 5@2012/02/22 23:53:26 +09:00 105

OnNext: plan2 12

OnNext: plan2 13

OnNext: plan1 6@2012/02/22 23:53:27 +09:00 106

OnNext: plan2 14

OnNext: plan2 15

OnNext: plan1 7@2012/02/22 23:53:28 +09:00 107

OnNext: plan2 16

OnNext: plan2 17

OnNext: plan1 8@2012/02/22 23:53:29 +09:00 108

OnNext: plan2 18

OnNext: plan1 9@2012/02/22 23:53:30 +09:00 109

OnNext: plan2 19

OnCompleted

Page 200: Reactive extensions入門v0.1

193

plan1から発行された値と、plan2から発行された値が混在して表示されていることが確認できま

す。このことからWhenで繋いだ Plan<TResult>から発行された値はMergeメソッドで合成し

たように、値が発行された順番で後続に流されていることがわかります。

6.11.3. まとめ

このように Andメソッドは使って Zipメソッドのように複数の IObservable<T>のシーケンスを

合成して Thenメソッドで値の変換を行う Plan<TResult>クラスのンスタンスを作成します。

そしてWhenメソッドに Plan<TResult>を複数渡すことで Andで繋いだものをMergeメソッド

で合成したように繋げることが出来ます。

恐らく合成系のメソッドの中で一番自由度が高く複雑な合成が出来るメソッドになります。

7. Scheduler

ここでは、Reactive Extensionsの特徴的な機能の1つ Schedulerについて説明します。Reactive

Extensionsでは、Schedulerという仕組みを使って IObservable<T>のシーケンスの処理の実行

場所と時間を自在に制御することができます。この機能を提供するために Reactive Extensions

では、ISchedulerというンターフェースを定義しています。ISchedulerンターフェースは下

記のような 1つのプロパテゖと 3つのメソッドを持つシンプルなンターフェースです。

public interface IScheduler

{

DateTimeOffset Now { get; }

public IDisposable Schedule<TState>(

TState state,

DateTimeOffset dueTime,

Func<IScheduler, TState, IDisposable> action);

public IDisposable Schedule<TState>(

TState state,

TimeSpan dueTime,

Func<IScheduler, TState, IDisposable> action);

public IDisposable Schedule<TState>(

TState state,

Func<IScheduler, TState, IDisposable> action);

}

ISchedulerンターフェースの Nowプロパテゖがスケジューラ上の現在の時間を表し、

Scheduleメソッドで Schedulerで実行する処理を設定します。実際に ISchedulerンターフェ

Page 201: Reactive extensions入門v0.1

194

ースを実装することや、ここで定義されているメソッドを使用することは、ほとんどありませんが、

基本としては上記のようなものを使用しているという点はなんとなく頭の片隅に入れておいてく

ださい。

7.1. 実行場所の切り替え

Reactive Extensionsは実行場所を制御するための ISchedulerンターフェースの実装クラスが

いくつか定義されています。Reactive Extensionsで定義されている実装クラスを以下に示します。

ImmediateScheduler

現在のスレッド上で即座に処理を実行する Schedulerです。

CurrentThreadScheduler

現在のスレッド上で処理を逐次実行する Schedulerです。

ThreadPoolScheduler

ThreadPool上で処理を実行する Schedulerです。

NewThreadScheduler

新規スレッドを作り、その上で処理を実行する Schedulerです。

TaskPoolScheduler

TPLの Task上で処理を実行する Schedulerです。

上記の Schedulerは System.Reactive.Concurrency.Schedulerクラスの staticプロパテゖとし

てンスタンスが提供されています。ImmediateSchedulerと CurrentThreadSchedulerは、ど

ちらも現在のスレッド上で処理を実行しますが、ImmediateSchedulerが、依頼された処理をそ

の場で実行するのに対して、CurrentThreadSchedulerは、依頼された処理をキューに追加して

順次実行していきます。動作の違いを示すコード例を下記に示します。

Console.WriteLine("CurrentThread");

// ChrrentThreadSchedulerで処理を実行

Scheduler.CurrentThread.Schedule(() =>

{

Console.WriteLine("Start");

// 処理の内部でさらに処理を CurrentThreadSchedulerに依頼

Scheduler.CurrentThread.Schedule(() =>

{

Console.WriteLine(" Start");

Console.WriteLine(" End");

});

Console.WriteLine("End");

});

Console.WriteLine("Immediate");

// ImmediateSchedulerで処理を実行

Page 202: Reactive extensions入門v0.1

195

Scheduler.Immediate.Schedule(() =>

{

Console.WriteLine("Start");

// 処理の内部でさらに処理を ImmediateSchedulerに依頼

Scheduler.Immediate.Schedule(() =>

{

Console.WriteLine(" Start");

Console.WriteLine(" End");

});

Console.WriteLine("End");

});

CurrentThreadSchedulerと ImmediateSchedulerの双方で、Scheduleメソッドを再起呼び出

ししています。このコードの実行結果を以下に示します。

CurrentThread

Start

End

Start

End

Immediate

Start

Start

End

End

CurrentThreadSchedulerが、Scheduleメソッドで登録した処理を現在実行中の処理が終了して

から実行しているのに対して、ImmediateSchedulerが、Scheduleメソッドで登録した処理を現

在実行中の処理を中断して実行している(通常のメソッド呼び出しをしたのと同じ)ことが確認で

きます。

7.2. IObservable<T>生成時の Scheduler の指定方法

IObservable<T>のシーケンスを生成するメソッドの大半には ISchedulerンターフェースを受

け取るオーバーロードがあります。このオーバーロードに ISchedulerの実装クラスのンスタン

スを渡すことで実行場所を設定できます。Timerメソッドを例にスケジューラの指定方法を示し

ます。コード例を下記に示します。

Console.WriteLine("Normal");

// 開始のログ

Console.WriteLine("{0:HH:mm:ss}: Start!!", DateTime.Now);

// 1秒後に値を発行する

Page 203: Reactive extensions入門v0.1

196

Observable.Timer(TimeSpan.FromSeconds(1))

.Subscribe(

i => Console.WriteLine("OnNext({0})", i),

() => Console.WriteLine("OnCompleted"));

// 終了のログ

Console.WriteLine("{0:HH:mm:ss}: End!!", DateTime.Now);

// Enterが押されるまで待つ

Console.ReadLine();

Console.WriteLine("CurrentThreadScheduler");

// 開始のログ

Console.WriteLine("{0:HH:mm:ss}: Start!!", DateTime.Now);

// 1秒後に値を発行する, 実行場所は現在のスレッド

Observable.Timer(TimeSpan.FromSeconds(1), Scheduler.CurrentThread)

.Subscribe(

i => Console.WriteLine("OnNext({0})", i),

() => Console.WriteLine("OnCompleted"));

// 終了のログ

Console.WriteLine("{0:HH:mm:ss}: End!!", DateTime.Now);

// Enterが押されるまで待つ

Console.ReadLine();

Timerメソッドを 2回呼び出して発行された値をコンソールに出力しています。最初の Timerメ

ソッドの呼び出しは、Schedulerを指定しないものです。2つ目の Timerメソッドの呼び出しで

は Scheduler.CurrentThreadを指定しています。このようにメソッドの最後の引数に Scheduler

を渡すことで、生成された IObservable<T>のシーケンスが何処で実行されるのか指定できます。

実行結果を以下に示します。

Normal

20:25:22: Start!!

20:25:22: End!!

OnNext(0)

OnCompleted

CurrentThreadScheduler

20:25:26: Start!!

OnNext(0)

OnCompleted

20:25:27: End!!

Page 204: Reactive extensions入門v0.1

197

CurrentThreadSchedulerを指定したほうでは、OnNextと OnCompletedが現在のスレッド上

で実行されるため、Start!!と End!!の実行順序が Schedulerを指定しない場合と異なっているこ

とが確認できます。

7.2.1. デフォルトの Scheduler

Timerメソッドや Intervalメソッドのように、現在のスレッドをブロックしない形の

IObservable<T>のシーケンスを生成するメソッドではデフォルトの Schedulerは

ThreadPoolSchedulerが指定されています。また、現在のスレッドをブロックする

IObservable<T>のシーケンスを生成するメソッドでは ImmediateSchedulerや

CurrentThreadSchedulerが指定されています。

7.3. Scheduler の切り替え

ここでは、IObservable<T>のシーケンスの処理中に Schedulerを切り替える方法について説明

します。

7.3.1. ObserveOn メソッド

IObservable<T>のシーケンスの処理中に Schedulerを切り替えるには ObserveOnメソッドを

使用します。ObserveOnメソッドは IObservable<T>の拡張メソッドとして定義されています。

メソッドのシグネチャを以下に示します。

public static IObservable<TSource> ObserveOn<T>(

this IObservable<T> source,

IScheduler scheduler);

public static IObservable<T> ObserveOn<TSource>(

this IObservable<T> source,

SynchronizationContext context);

ISchedulerを受け取るオーバーロードは、後続の処理を指定した Scheduler経由で実行するメソ

ッドです。SynchronizationContextを受け取るオーバーロードは、SynchronizationContext経

由で後続の処理を実行するメソッドです。実際には、SynchronizationContextSchedulerという

クラスが Reactive Extensionsで定義されているので、このメソッドの意味合いは、

ObserveOn(new SynchronizationContextScheduler(<SynchronizationContextのンスタン

ス>))へのショートカットのメソッドです。ObserveOnメソッドのコード例を下記に示します。

// 1秒間隔で 3つ値を発行する

Observable

.Interval(TimeSpan.FromSeconds(1))

.Take(3)

// 現在のスレッド IDを表示

.Do(_ => Console.WriteLine("ThreadId: {0}", Thread.CurrentThread.ManagedThreadId))

// 実行スレッドを新しいスレッドに切り替える

.ObserveOn(Scheduler.NewThread)

// 現在のスレッド IDを表示

Page 205: Reactive extensions入門v0.1

198

.Do(_ => Console.WriteLine("ThreadId: {0}", Thread.CurrentThread.ManagedThreadId))

// 購読して、発行された値とスレッド IDを表示

.Subscribe(

i => Console.WriteLine("OnNext({0}), ThreadId: {1}", i,

Thread.CurrentThread.ManagedThreadId),

() => Console.WriteLine("OnCompleted, ThreadId: {0}",

Thread.CurrentThread.ManagedThreadId));

Console.ReadLine();

Timerメソッドと Takeメソッドを使って 1秒間隔で 3つの値を発行しています。そして

ObserveOnメソッドを使ってスレッドを新規スレッドに切り替えています。スレッドの切り替え

前後で Doメソッドを使ってスレッド IDを表示しています。Subscribeメソッドでもスレッド ID

を表示して実行スレッドがどうなっているのか確認しています。実行結果を以下に示します。

ThreadId: 4

ThreadId: 5

OnNext(0), ThreadId: 5

ThreadId: 4

ThreadId: 7

OnNext(1), ThreadId: 7

ThreadId: 6

ThreadId: 8

OnNext(2), ThreadId: 8

OnCompleted, ThreadId: 9

スレッド IDが切り替わっていることが確認できます。このことから、IObservable<T>のシーケ

ンスの途中で実行スレッドの切り替えができることがわかります。

7.3.2. ObserveOn の使用用途

実行場所(ここでの例では実行スレッド)の切り替えが、どのような用途で使えるのかについて説

明します。代表的な用途では、Windows FormsやWPFなどのような UIを操作するケースです。

たとえば、ボタンクリックのベントなどをトリガーにバックグラウンドでWeb APIを呼び出し

て、結果を画面に反映させる操作があるとします。

このとき、ベントの購読とベントの値の加工程度までは UIスレッドで行い、Web APIを呼

び出す箇所はバックグラウンドで行うのがUIをフリーズさせないようにするため最近では一般的

な方法です。そして、最後の UIへの結果の反映で、再び UIスレッドに切り替えて処理を行いま

す。これは、UIフレームワークが一般的に UIスレッド以外からの操作に対応していないために必

要になります。このように「ベントの発火→非同期での処理の呼び出し→結果の処理」といった

Reactive Extensionsで 1シーケンスで記述できるような処理内でのスレッドの切り替えに

ObserveOnメソッドは効果を発揮します。

Page 206: Reactive extensions入門v0.1

199

7.4. 時間を制御する Scheduler

Schedulerには、実行場所を制御するもののほかに、時間を制御するタプの Schedulerがあり

ます。実際の処理内部では、あまり出てくる頻度は高くないですがテスト時に 24時間かかる処理

を 24時間待たずに一瞬で 24時間後の状態にするといったことも可能なため、このようなものも

あるのだと把握しておいてください。

7.4.1. HistoricalSchedulerクラス

時間を制御するSchedulerクラスとしてHistoricalSchedulerを紹介します。HistoricalScheduler

は AdvancedByメソッドや AdvancedToメソッドを使って Scheduler内の時間を制御します。

AdvancedByが TimeSpan型で AdvancedToメソッドが DateTimeOffcetを使って時間を指定

します。HistoricalSchedulerクラスの使用例を下記に示します。

var scheduler = new HistoricalScheduler();

// 1秒間隔で値を発行する

Observable.Interval(TimeSpan.FromSeconds(1), scheduler)

// 購読

.Subscribe(

i => Console.WriteLine("OnNext({0})", i),

() => Console.WriteLine("OnCompleted"));

// HistoricalSchedulerを使ってプログラム内で Schedulerの時間を進める

// 0.5sec時間を進める

Console.WriteLine("AdvanceBy(0.5sec)");

scheduler.AdvanceBy(TimeSpan.FromSeconds(0.5));

// 0.5sec時間を進める

Console.WriteLine("AdvanceBy(0.5sec)");

scheduler.AdvanceBy(TimeSpan.FromSeconds(0.5));

// 0.5sec時間を進める

Console.WriteLine("AdvanceBy(0.5sec)");

scheduler.AdvanceBy(TimeSpan.FromSeconds(0.5));

// 0.5sec時間を進める

Console.WriteLine("AdvanceBy(0.5sec)");

scheduler.AdvanceBy(TimeSpan.FromSeconds(0.5));

// 0.5sec時間を進める

Console.WriteLine("AdvanceBy(0.5sec)");

scheduler.AdvanceBy(TimeSpan.FromSeconds(0.5));

Observable.Intervalメソッドを使って 1秒間隔で値を発行する IObservable<T>のシーケンス

を作成しています。このときの Schedulerに HistoricalSchedulerを指定しています。そして

Page 207: Reactive extensions入門v0.1

200

AdvanceByメソッドを使ってプログラム内で Schedulerの時間を進めています。0.5秒×5回の

時間を進めているので合計で 2.5秒の時間が進んでいることになります。実行結果を以下に示し

ます。

AdvanceBy(0.5sec)

AdvanceBy(0.5sec)

OnNext(0)

AdvanceBy(0.5sec)

AdvanceBy(0.5sec)

OnNext(1)

AdvanceBy(0.5sec)

AdvanceByを 2回呼んだ時点で値が発行され、さらに AdvanceByを 2回呼んだ時点で値が発行

されていることが確認できます。このように Reactive Extensionsでは時間を Scheduler経由で

取得するようにすることで、プログラムから柔軟に時間を制御することができるようになっていま

す。

8. 応用編

ここでは、これまでに紹介した Reactive Extensionsのメソッドを組み合わせていくつか実用的

な例をプログラミングしてみようと思います。

8.1. センサー監視

下記の要件を満たすプログラムを作成します。

10個のセンサーがあります。10個のセンサーは、それぞれに識別用の名前が与えられていて 1秒間隔で数値(1

~1000)を上げてきます。プログラムでは 10秒間の間に一番大きな値を上げてきたセンサーの名前と値をコンソ

ールに出力します。

ということで作成していきます。実際にセンサーは無いので下記のような疑似的なセンサーを表す

クラスをセンサーに見立ててプログラムを書きます。

namespace SensorSample

{

using System;

using System.Threading;

// センサーを表すクラス

class Sensor

{

// センサーの値はとりあえず乱数で発行する

private static Random random = new Random();

// センサーの値を発行する間隔を制御するタマー

Page 208: Reactive extensions入門v0.1

201

private Timer timer;

// センサー名

public string Name { get; private set; }

// 名前をつけてセンサー作成

public Sensor(string name)

{

this.Name = name;

}

// センサーの値の発行開始

public void Start()

{

this.timer = new Timer(_ =>

{

this.OnPublish(random.Next(1001));

},

null,

0,

1000);

}

// センサーの値発行ベント

public EventHandler<SensorEventArgs> Publish;

// センサーから値を発行する

private void OnPublish(int value)

{

var h = this.Publish;

if (h != null)

{

h(this, new SensorEventArgs(this.Name, value));

}

}

}

Page 209: Reactive extensions入門v0.1

202

// センサーが値を発行したときのベント引数

class SensorEventArgs : EventArgs

{

// 値を発行したセンサー名

public string Name { get; private set; }

// 発行した値

public int Value { get; private set; }

public SensorEventArgs(string name, int value)

{

this.Name = name;

this.Value = value;

}

}

}

このプログラムをベースに Reactive Extensionsを使ってプログラムを書いていきます。Mainメ

ソッドの中身を下記に示します。

// 10個のセンサーを作成

var sensors = Enumerable.Range(1, 10).Select(i => new Sensor("Sensor#" + i)).ToArray();

// 10個のセンサーの値発行ベントをマージ

var subscription = Observable.Merge(

sensors.Select(sensor => Observable.FromEvent<EventHandler<SensorEventArgs>,

SensorEventArgs>(

h => (s, e) => h(e),

h => sensor.Publish += h,

h => sensor.Publish -= h)))

// 10秒ためて

.Buffer(TimeSpan.FromSeconds(10))

// その中から最大のものを探して

.Select(values => values.Aggregate((x, y) => x.Value > y.Value ? x : y))

// 表示する

.Subscribe(e => Console.WriteLine("{0}: {1}", e.Name, e.Value));

// センサースタート

foreach (var sensor in sensors)

{

sensor.Start();

Page 210: Reactive extensions入門v0.1

203

}

Console.WriteLine("Sensor started.");

Console.ReadLine();

// 最後にセンサーの Publishベントの購読解除

subscription.Dispose();

時間やベントを扱う Reactive Extensionsの得意分野なので非常にすっきりしたコードになっ

ているのがわかります。恐らく普通にプログラムを書くとかなりの分量になると思われます。10

秒間分の値を保持して最大値を探して etc…考えただけでも恐ろしくなります。

このプログラムの実行結果を下記に示します。

Sensor started.

Sensor#6: 994

Sensor#7: 997

Sensor#3: 1000

Sensor#1: 982

Sensor#4: 986

Sensor#4: 964

続行するには何かキーを押してください . . .

実行結果の字面上ではわかりませんが、10秒間隔で 1行ずつ結果が表示されます。

9. 参考サイト

本ドキュメントを書くにあたって参考にしたサト等を紹介します。

neue cc : http://neue.cc/

MS MVP for C#の neueccさんのサトです。日本語情報で一番濃い Reactive Extensions

に関する情報が読めるサトです。特に下記の勉強用リソースまとめは、参考になると思いま

す。

Reactive Extensionsを学習するためのリソースまとめ

http://neue.cc/2011/01/26_300.html

連載:Reactive Extensions(Rx)入門 :

http://www.atmarkit.co.jp/fdotnet/introrx/index/index.html

@ITに neueccさんが書いている Reactive Extensionsの連載記事になります。

xin9le note Rx入門ンデックス:

http://xin9le.blogspot.com/2011/12/rx.html

Hokuriku.NETなどでスピーカーとして活躍されている@xin9leさんの入門記事です。

Page 211: Reactive extensions入門v0.1

204

Reactive Extensions : http://msdn.microsoft.com/en-us/data/gg577609

Reactive Extensionsの公式サトです。英語情報ですが、様々な情報ソースへのリンクがあ

ります。

Reactive Extensions Team Blog : http://blogs.msdn.com/b/rxteam/

Reactive Extensions Teamによる公式ブログです。最新のリリース情報等が記載されて

います。

Reactive Extensions Forum :

http://social.msdn.microsoft.com/Forums/en-US/rx/threads

MSDN Forum内の Reactive Extensions Forumです。英語で活発なデゖスカッション

が行われています。

Reactive Extensions for .NET Resources and Community :

http://msdn.microsoft.com/ja-jp/data/gg577612

様々な情報へのリンクが集まっています。HoLから読むのがいいような気がします。

Rx on Channel 9 : http://channel9.msdn.com/tags/Rx/

Reactive Extensionsについて説明している Channel9内の動画です。日本語だと非常に

わかりやすいのですがいかんせん英語で説明されても 80%以上何を言っているのかわか

らない・・・。