かずきのBlog@hatena

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

Reactive Extensions再入門 その19「AnyメソッドとAllメソッド」

過去記事インデックス

はじめに

今回は、AnyメソッドとAllメソッドについて紹介します。どちらもIObservableをIObservableとして集計結果を返してくれるメソッドです。意外と使うことがあるので挙動を押さえておきましょう!

Anyメソッド

次にAnyメソッドについて説明します。Anyメソッドは、引数で渡したデリゲートがtrueを返す要素が1つでもあればtrueを後続に流します。IObservableのシーケンスが完了した時点で、どれもtrueにならなかった場合にはfalseを返します。コード例を下記に示します。

var s = new Subject<int>();
// どれかが0以下かチェック
s.Any(i => i <= 0)
    // 購読
    .Subscribe(
        i => Console.WriteLine("Any(i => i <= 0) OnNext({0})", i),
        () => Console.WriteLine("Any(i => i <= 0) OnCompleted()"));

// どれかが偶数かチェック
s.Any(i => i % 2 == 0)
    // 購読
    .Subscribe(
        i => Console.WriteLine("Any(i => i % 2 == 0) OnNext({0})", i),
        () => Console.WriteLine("Any(i => i % 2 == 0) OnCompleted()"));

// 値の発行〜完了通知
Console.WriteLine("OnNext(1)");
s.OnNext(1);
Console.WriteLine("OnNext(10)");
s.OnNext(10);
Console.WriteLine("OnNext(100)");
s.OnNext(100);
Console.WriteLine("OnCompleted()");
s.OnCompleted();

IObservableに対してAnyメソッドを2回呼び出しています。最初のAnyメソッドには渡ってきた値が0以下かどうかを判定するデリゲートを渡しています。2つ目のAnyメソッドには渡ってきた値が偶数かどうかを判定するデリゲートを渡しています。そして、1, 10, 100という3つの値をIObservableのシーケンスに流してOnCompletedでシーケンスを終了させています。実行結果を下記に示します。

OnNext(1)
OnNext(10)
Any(i => i % 2 == 0) OnNext(True)
Any(i => i % 2 == 0) OnCompleted()
OnNext(100)
OnCompleted()
Any(i => i <= 0) OnNext(False)
Any(i => i <= 0) OnCompleted()

注目すべき点は、偶数の値が流れてきた時点で、Trueが後続に流れている点です。OnNext(10)の後にすぐログが出ていることが確認できます。一方、0以下の値は1つもIObservableのシーケンスに流していないためOnCompleted()が呼び出された後にFalseが流れていることが確認できます。

Allメソッド

次は、Allメソッドについて説明します。AllメソッドはAnyメソッドと異なり引数で渡したデリゲートが全てTrueになるかどうかを確認します。コード例を下記に示します。

var s = new Subject<int>();
// 全てが偶数かどうかをチェック
s.All(i => i % 2 == 0)
    // 購読
    .Subscribe(
        i => Console.WriteLine("All(i => i % 2 == 0) OnNext({0})", i),
        () => Console.WriteLine("All(i => i % 2 == 0) OnCompleted()"));

// 全てが1000以下かどうかをチェック
s.All(i => i <= 1000)
    // 購読
    .Subscribe(
        i => Console.WriteLine("All(i => i <= 1000) OnNext({0})", i),
        () => Console.WriteLine("All(i => i <= 1000) OnCompleted()"));

// 値の発行〜完了通知
Console.WriteLine("OnNext(1)");
s.OnNext(1);
Console.WriteLine("OnNext(10)");
s.OnNext(10);
Console.WriteLine("OnNext(100)");
s.OnNext(100);
Console.WriteLine("OnCompleted()");
s.OnCompleted();

IObservableのシーケンスに対して2回Allメソッドを呼び出しています。最初の呼び出しでは、偶数かどうかを判定するデリゲートを渡しています。2つ目の呼び出しでは、1000以下かどうかを判定するデリゲートを渡しています。この例もAllメソッドがどの時点で後続に対して結果を流しているかがポイントになります。実行結果を下記に示します。

OnNext(1)
All(i => i % 2 == 0) OnNext(False)
All(i => i % 2 == 0) OnCompleted()
OnNext(10)
OnNext(100)
OnCompleted()
All(i => i <= 1000) OnNext(True)
All(i => i <= 1000) OnCompleted()

偶数かどうかを判断するデリゲートを渡したAllメソッドは、OnNext(1)が呼び出された段階でFalseになることが確定するので後続にFalseを流しています。1000以下かどうかを判断するデリゲートを渡したAllメソッドは1, 10, 100の値では、どれもFalseにならないためOnCompleted()が呼び出された時点でTrueになることが確定して、後続にTrueを流しています。AnyメソッドもそうですがAllメソッドは、流れてきた値にたいしてリアルタイムに反応できる点がとてもReactive Extensionsらしい特徴のメソッドになっています。