過去記事インデックス
- Reactive Extensions再入門 その1
- Reactive Extensions再入門 その2「IObservableインターフェースとIObserverインターフェース」
- Reactive Extensions再入門 その3「IObservableのファクトリメソッド」
- Reactive Extensions再入門 その4「Timer系のファクトリメソッド」
- Reactive Extensions再入門 その5「HotとCold」
- Reactive Extensions再入門 その6「HotなIObservableを作成するファクトリ」
- Reactive Extensions再入門 その7「LINQスタイルの拡張メソッド」
- Reactive Extensions再入門 その8「SkipとTakeメソッド」
- Reactive Extensions再入門 その9「Skip + Take + Repeat = ドラッグ」
- Reactive Extensions再入門 その10「Doメソッド」
- Reactive Extensions再入門 その11「Catchメソッド」
- Reactive Extensions再入門 その12「Finallyメソッドとリソース解放」
- Reactive Extensions再入門 その13「最後の値を取得するLatestとMostRecentメソッド」
- Reactive Extensions再入門 その14「Nextメソッド」
- Reactive Extensions再入門 その15「To*****系メソッド」
- Reactive Extensions再入門 その16「最大、最少、平均を求めるメソッド」
- Reactive Extensions再入門 その17「集計するメソッド」
- Reactive Extensions再入門 その18「CountメソッドとLongCountメソッド」
- Reactive Extensions再入門 その19「AnyメソッドとAllメソッド」
- Reactive Extensions再入門 その20「GroupByメソッドでグルーピングしてみよう」
- Reactive Extensions再入門 その21「GroupByUntilメソッド」
はじめに
2012年最初のBlogネタはReactive Extensions!!地味にいくよ!ということで今年もよろしくお願いします。
単一の値を取得するメソッド
ここでは、IObservable
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
// 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);
5秒間隔で値を発行するIObservable
Timestamp 2012/01/03 18:25:45.783 Timestamp 2012/01/03 18:25:50.829 firstResult: value is 0
三行目に、Firstメソッドで取得した値が出力されていることが確認できます。このように、Firstメソッドでは、IObservable
Firstメソッドの特徴として、最初の値がIObservable
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
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秒間スレッドをブロックしています。
値が存在しない場合の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
// 1つも要素の無いIObservable var noElementsSequence = new Subject<string>(); noElementsSequence.OnCompleted(); try { // 最後の要素の取得 var lastResult = noElementsSequence.Last(); } catch (InvalidOperationException e) { // 何もないので例外が発生する Console.WriteLine("Exception: {0}", e.Message); }
Exception: Sequence contains no elements.
FirstメソッドとLastメソッドのオーバーロード
FirstメソッドとLastメソッドには、Func
FirstOrDefaultメソッドとLastOrDefaultメソッド
ここでは、FirstOrDefaultメソッドとLastOrDefaultメソッドについて説明します。このメソッドもFirstメソッドやLastメソッドと同様にIObservable
まずは、値が取得できるケースの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 Timestamp 2012/01/03 18:55:13.623 firstResult: value is 0
実行結果もFirstメソッドと変わりません。次に、空のIObservable
// 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メソッドについても同様の動きとなるため、コード例と実行結果は割愛します。
FirstOrDefaultメソッドとLastOrDefaultメソッドのオーバーロード
FirstOrDefaultメソッドとLastOrDefaultメソッドにもFirstメソッドとLastメソッドと同様にFunc
ElementAtメソッド
ここでは、ElementAtメソッドについて説明します。ElementAtメソッドはインデックスで指定した要素を取得するメソッドになります。FirstメソッドやLastメソッドが単一の値を返していたのに対してElementAtメソッドの戻り値はIObservable
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前のタイムスタンプを表示 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
コードの本題とは関係ありませんが、EventWaitHandleクラスを使ってGenerateメソッドで作成したIObservable
コードの実行結果を下記に示します。
Timestamp 2012/01/03 19:13:33.498 Timestamp 2012/01/03 19:13:33.538 elementAt3: value is 3 OnCompleted Timestamp 2012/01/03 19:13:37.588
実行結果の3行目でvalue is 3と3番目の値が表示されていることが確認できます。また、最初の2つのタイムスタンプの表示は、ElementAtを含む一連のメソッドチェインの前後で出力していますが、ほとんど時間が経過していないことがわかります。そして、最後のタイムスタンプの出力までに4秒の時間が経過していることが確認できます。1つの値が1秒間隔で発行されているため3番目(0オリジンなので正確には4番目)の値が発行されるまでの時間と合致しています。
値が存在しない場合のElementAtメソッドの挙動
ElementAtで指定したインデックスに値が存在しない場合の挙動について説明します。この場合もFirstメソッドやLastメソッドと同様に例外が発生しますが、IObservable
// 待機用の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);
実行結果を下記に示します。
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が発生します。
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(); // gate.Setが呼ばれた後のタイムスタンプを表示する Console.WriteLine("Timestamp {0:yyyy/MM/dd HH:mm:ss.FFF}", DateTime.Now);
0〜2の3つの要素しか発行していないIObservable
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と表示されていることから、範囲外の値を要求しても例外が発生せずにデフォルト値が発行されていることが確認できます。