読者です 読者をやめる 読者になる 読者になる

xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

Rx入門 (24) - まとめてDispose

Rx

「Hot Observableなどに関連付けした複数のオブザーバーを一括して解除したい!」と思ったことはありませんか?今回はこれの実装を少しばかり簡単にする方法について見ていきたいと思います。

List<T>などを利用する場合

まず、一般的なコレクションを利用した例として、List<T>を用いたサンプルを示します。やっていることは簡単で、購読解除を行うためのIDisposableオブジェクトをコレクションに登録したり、削除したりしているだけです。

using System;
using System.Collections.Generic;
using System.Reactive.Subjects;
 
namespace ConsoleApplication
{
    class Program
    {
        static void Main()
        {
            var subject    = new Subject<int>();
            var collection = new List<IDisposable>();
            collection.Add(subject.Subscribe(value => Console.WriteLine("A : OnNext({0})", value)));
            collection.Add(subject.Subscribe(value => Console.WriteLine("B : OnNext({0})", value)));
            subject.OnNext(0);
 
            Console.WriteLine("----- Aを解除 -----");
            collection[0].Dispose(); //--- 解除して
            collection.RemoveAt(0);  //--- コレクションから削除
            subject.OnNext(1);
 
            Console.WriteLine("----- Cを追加 -----");
            collection.Add(subject.Subscribe(value => Console.WriteLine("C : OnNext({0})", value)));
            subject.OnNext(2);
 
            Console.WriteLine("----- 全解除 -----");
            foreach (var subscription in collection)
                subscription.Dispose();
            collection.Clear(); //--- ループしてから消すしかない
            subject.OnNext(3);
        }
    }
}
 
//----- 結果
/*
A : OnNext(0)
B : OnNext(0)
----- Aを解除 -----
B : OnNext(1)
----- Cを追加 -----
B : OnNext(2)
C : OnNext(2)
----- 全解除 -----
*/

購読解除を行う場合、中の要素をDisposeしてからコレクションから除外するという手順になります。しかし、毎回これだと煩わしいので便利なヘルパークラスが用意されています。

CompositeDisposable

Rxのライブラリには、IDisposableオブジェクト専用のコレクションとしてCompositeDisposableクラスが用意されています。以下にそれを利用して上記のサンプルを書きなおした例を示します。

using System;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
 
namespace Sample43_CompositeDisposable
{
    class Program
    {
        static void Main()
        {
            var subject   = new Subject<int>();
            var disposer1 = subject.Subscribe(value => Console.WriteLine("A : OnNext({0})", value));
            var disposer2 = subject.Subscribe(value => Console.WriteLine("B : OnNext({0})", value));
            using (var collection = new CompositeDisposable(disposer1, disposer2)) //? 登録
            {
                subject.OnNext(0);
 
                Console.WriteLine("----- Aを解除 -----");
                collection.Remove(disposer1); //--- 削除時にDisposeされる
                subject.OnNext(1);
 
                Console.WriteLine("----- Cを追加 -----");
                collection.Add(subject.Subscribe(value => Console.WriteLine("C : OnNext({0})", value)));
                subject.OnNext(2);
            } //--- スコープを抜けるとすべてDispose
            Console.WriteLine("----- 全解除 -----");
            subject.OnNext(3); //--- 解除済みなので何も起こらない
        }
    }
}

CompositeDisposableクラスはそれ自身がIDisposableインターフェースを実装しています。最大のポイントは、自身のDispose時に登録されている要素すべてに対してDisposeメソッドを実行してくれることです。煩わしかったforeach文を消し去ることができるだけでなく、usingステートメントによって有効期間をスコープとして明示できるというメリットもあります。また、Removeメソッドでコレクションから要素を削除する場合もDisposeメソッドを自動で呼び出してくれます。

CompositeDisposableクラスはRxのライブラリの一部として提供されていますが、他にも一般的に利用できる箇所がある気がするので、ぜひ存在だけでも覚えておきましょう。

その他のDisposableオブジェクト

RxライブラリのSystem.Reactive.Disposables名前空間には、CompositeDisposableクラス以外にもいくつかの便利クラスが提供されています。これらについては「Rx入門 (28) – Disposables名前空間まとめ」で挙動をまとめましたので、合わせてご覧頂ければ幸いです。