過去記事インデックス
- 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メソッド」
- Reactive Extensions再入門 その22「単一の値を取得するメソッド」
- Reactive Extensions再入門 その23「重複を排除するメソッド」
はじめに
単一の値を取得する系メソッドの紹介の「その2」です!何故初回で全部やらなかったかというと、素直にここで紹介するSingleメソッドの存在を忘れていましたorz
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 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がスローされていることが確認できます。
Singleメソッドのオーバーロード
SingleメソッドにはFirstメソッドやLastメソッドやElementAtメソッドと同様に、値のフィルタリングを行うためのFunc
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が取得できています。
このオーバーロードを使った場合も、引数で指定した条件にあうものが複数ある場合や、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
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
SingleOrDefaultメソッド
ここでは、SingleOrDefaultメソッドについて説明します。このメソッドはSingleメソッドと同様にIObservable
// 実行開始時間を出力 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(); // 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);
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つ目の値が発行されたタイミングで例外が発生していることが確認できます。
SingleOrDefaultメソッドのオーバーロード
SingleOrDefaultメソッドにもSingleメソッドと同様にFunc
動作はSingleOrDefaultとSingleメソッドのオーバーロードで示したものと、ほぼ同じような形になるため、コード例については割愛します。