かずきのBlog@hatena

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

Reactive Extensions再入門 その14「Nextメソッド」

はじめに

今回は、前回やったLatestとMostRecentの仲間であるNextメソッドの紹介を行います。まず最初に、NextメソッドとLatestメソッドの挙動の違いを示すために必要なBehaviorSubjectの説明を行い、その後BehaviorSubjectを使用してLatestメソッドとNextメソッドの違いを確認します。

BehaviorSubjectクラス

BehaviorSubjectクラスは、初期値を持つSubjectクラスになります。BehaviorSubjectクラスのインスタンスを作成するときにコンストラクタの引数で初期値を渡します。SubjectクラスではSubscribeするだけでは値は発行されませんでしたが、BehaviorSubjectクラスは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()

Subscribeをした時点で、コンストラクタで渡した初期値の0が通知されていることが確認できます。また、BehaviorSubjectは、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が呼ばれていることが確認できます。

Nextメソッド

次に、LatestメソッドとMostRecentメソッドと同様にIObservableからIEnumerableへ変換するNextメソッドについて説明します。NextメソッドはLatestメソッドと同じようにMoveNextで値が発行されるのを待機するIEnumerableを返します。Latestメソッドが返すIEnumerableとの違いは、Latestは最後の値を1回だけキャッシュしますがNextメソッドが返すIEnumerableは、キャッシュを行いません。この挙動の違いを示すプログラムを下記に示します。

// 初期値 -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(() =>
{
    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");

上記プログラムの実行結果を下記に示します。

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
Latest : 3
OnNext(4)
Latest : 4
Next : 4

End NextSample

※1の箇所にある通りLatestでは最後の値をキャッシュするためBehaviorSubjectの初期値である-1を表示しています。それに対してNextメソッドでは、初期値の-1は表示せずに1からの表示になっていることが確認できます。