かずきのBlog@hatena

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

Reactive Extensions再入門 その7「LINQスタイルの拡張メソッド」

修正履歴
2011/11/13
アップキャストをダウンキャストに修正

IObservableの拡張メソッド

ここまでIObservableを作成するための様々なファクトリメソッドを見てきました。ここでは、視点を変えてIObservableを作成したあとに使用できるIObservableに定義された拡張メソッドを紹介します。

LINQのメソッド

IObservableの拡張メソッドも、ほとんどがSystem.Reactive.Linq.Observableクラスに定義されています。その中でもLINQでお馴染みのWhereやSelectメソッドも含まれています。LINQのメソッドはIObservableが発行した値に対してWhereでフィルタリングしたりSelectで変換したりできます。下図は、そのイメージを表しています。

下図はWhereでフィルタリングされた場合を表しています。Whereでフィルタリングされた場合はSelectやSubscribeまで処理はいきません。

実際にコードで動きを確認してみます。

// 値を発行するためのSubject
var subject = new Subject<int>();
// AsObservableでIObservable<T>に変換(ダウンキャストでSubject<T>に戻せない
var source = subject.AsObservable();

// 普通にSubscribe
source.Subscribe(
    value => Console.WriteLine("1##OnNext({0})", value),
    ex => Console.WriteLine(ex.Message),
    () => Console.WriteLine("1##OnCompleted()"));

// 奇数のみ通すようにフィルタリングして
source.Where(i => i % 2 == 1)
    // 文字列に加工して
    .Select(i => i + "は奇数です")
    // 表示する
    .Subscribe(
        value => Console.WriteLine("2##OnNext({0})", value),
        ex => Console.WriteLine(ex.Message),
        () => Console.WriteLine("2##OnCompleted()"));

// 1〜10の値をsubjectに対して発行する
Observable.Range(1, 10).ForEach(i => subject.OnNext(i));
// 完了通知を行う
subject.OnCompleted();

上記のコードでは、1〜10の値をSubjectを使って発行しています。Subjectは、AsObservableメソッドでIObservableに変換できます。AsObservableをしなくてもSubjectクラスはIObservableを継承しているので差支えはないのですが、純粋なIObservableに、なんとなくしたかったのでこの例では変換しています。通常は、内部にSubjectクラスを抱えたクラスが外部にIObservableを公開するときに、ダウンキャストされてもSubject型に戻せないIObservableを返すために使用します。
その他に、今回初登場のメソッドとしてForEachメソッドがあります。これは引数に渡されたActionを、IObservableから発行された値を引数に渡して使用します。平たく言うとforループです。ここでは1〜10の値をObservable.Rangeで作成してForEachでSubjectに流し込んでいます。
今回の本題である拡張メソッドはWhereメソッドとSelectメソッドになります。Whereメソッドは引数で渡したFuncがtrueを返す要素のみを通します。Selectメソッドは引数で渡したFuncで値を変換します。上記の例では奇数以外の値をフィルタリングして「Xは奇数です」という文字列に変換して、Subscribe内で標準出力に出力しています。動作の違いを見るために、WhereやSelectを使用しないでSubscribeもしています。
このプログラムの実行結果を下記に示します。

1##OnNext(1)
2##OnNext(1は奇数です)
1##OnNext(2)
1##OnNext(3)
2##OnNext(3は奇数です)
1##OnNext(4)
1##OnNext(5)
2##OnNext(5は奇数です)
1##OnNext(6)
1##OnNext(7)
2##OnNext(7は奇数です)
1##OnNext(8)
1##OnNext(9)
2##OnNext(9は奇数です)
1##OnNext(10)
1##OnCompleted()
2##OnCompleted()

実行結果から、Whereによるフィルタリングが行われていることと、Selectによる変換が行われていることがわかると思います。

次回の予定

今回は、LINQのメソッドの中でも基本的なWhereとSelectを使用しました。LINQをしたことのある人なら自然に動作を理解できると思います。次回からは、そのほかの拡張メソッドも見ていこうと思います。