かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Reactive Extensions再入門 その26「値をまとめるWindowメソッド」

過去記事インデックス

はじめに

このシリーズもその26まで来ました。まだしばらく続きます。いくつまでいくのかな・・・。

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>なのに対してWindowメソッドはIObservable>になります。このシグネチャの違いが挙動にどう影響しているのかを比較するためのコードを下記に示します。
まずは、これまで使ってきたBufferメソッドのコード例を下記に示します。

// 値の発行元
var source = new Subject<int>();
source
    // 3つずつに纏める
    .Buffer(3)
    .Subscribe(
        l =>
        {
            // 値を表示
            Console.WriteLine("--Buffer");
            // IList<T>なのでループを使って値を出力
            foreach (var i in l)
            {
                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を使って、1〜4の値を発行しています。そして、Bufferメソッドで3つずつの値にまとめて表示しています。実行結果を以下に示します。

OnNext(1)
OnNext(2)
OnNext(3)
--Buffer
1
2
3
OnNext(4)
OnCompleted()
--Buffer
4
Buffer Completed

実行結果からわかるように、IObservableのシーケンスから値が3つ発行されるとBufferのSubscribeのOnNextが呼ばれて値が列挙されています。IObservableのシーケンスが終了すると、残った値をまとめてBufferのSubscribeのOnNextが呼ばれます。これがBufferメソッドでの挙動です。次に、このコードの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になっているためループからSubscribeに書き換えています。このコードの実行結果を以下に示します。

--Window
OnNext(1)
1
OnNext(2)
2
OnNext(3)
3
--Window
OnNext(4)
4
OnCompleted()
Window Completed

Bufferメソッドが、値が3つ揃ってからIListという形で後続に値を流しているのに対してWindowメソッドは、値が3つ揃う前から即座に後続に値を流していることが確認できます。Windowメソッドの戻り値がIObservable>になっているため、リアルタイムに値を監視して処理することが出来るようになっています。
そのためBufferメソッドは、値が全て揃ってから後続に値を流したい場合に、Windowメソッドは、値が発行されたら即座に後続に値を流したい場合に利用します。その他のオーバーロードに関しても挙動は同じになります。そのため、ここではその他のWindowメソッドのオーバーロードについての説明は割愛します。