過去記事インデックス
- 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「単一の値を取得するメソッド」
はじめに
ここでは、IObservable
Distinctメソッド
Distinctメソッドは、IObservable
// 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();
このコードでは、順番に1, 2, 3, 1, 2, 3, 2, 3, 4という値を発行するIObservable
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に渡っていることが確認できます。
Distinctメソッドのオーバーロード
Distinctメソッドには、Func
// 比較方法を指定するオーバーロード 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, IEqualityComparer<TKey> comparer )
この各種オーバーロードを使うことで、柔軟に重複値のフィルタリングを行えます。ここでは、最後のFunc
まず、サンプルコードで使用するクラスの定義を下記に示します。ここでは、名前と年齢をもった人を表すクラスと、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) { 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();
上記のコードの実行結果を下記に示します。
OnNext 10代〜30代 田中 一郎: 15歳 田中 二郎: 22歳 田中 三郎: 38歳 OnNext 10代〜30代 OnNext 40代 井上 エリザベス: 49歳 OnCompleted call. OnCompleted()
上記の結果からわかるとおり、PersonクラスのAgeプロパティの10の位の値で重複チェックが行われていることが確認できます。
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の値を発行 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