かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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をしたことのある人なら自然に動作を理解できると思います。次回からは、そのほかの拡張メソッドも見ていこうと思います。