R3のAddToまとめ

まだプレビュー版が外れていないR3ですが、もうそろそろ正式リリースされそうな雰囲気なので記事を書き始めようかと思います。
今回はUniRxのときと少しObservableに対する `AddTo` のシグネチャの変更や追加があるので紹介します。

### 実行環境
Unity 2022.3.17
~~R3 0.1.12~~ → R3 1.0.0

# `RegisterTo(CancellationToken)`
AddToにCancellationTokenを渡すのは UniTask でもありました。ですが、UniTaskのAddToと名前が競合するということで、R3ではCancellationTokenを渡す場合に限り `RegisterTo` メソッドで対応します。
内部実装的にもUniTaskと大きな違いはありません。登録したCancellationTokenがキャンセル状態になったときDisposeされる紐付けができます。`CancellationToken.Register` で `IDisposable.Dispose()` を呼ぶようになっています。

```c#:
void DoNanka(CancellationToken token)
{
    Observable.Timer(TimeSpan.FromSeconds(10))
              .Subscribe(_ => Debug.Log("Finish!"))
              .RegisterTo(token);
}
```

返り値が `CancellationTokenRegistration` になっているのでCancellationTokenとIDisposableの寿命を一度切り離すことができます。(購読自体を止められるわけではないので注意)

# `AddTo(MonoBehaviour)`
UniRxでよく使われていた `AddTo(GameObject)` と `AddTo(Component)` は廃止されました。
代わりに MonoBehaviour を渡すようになっています。

内部ではUnity2022.2 から登場した `MonoBehaviour.destroyCancellationToken` を呼び出すショートカットになっていて、これを渡しても同じ挙動になります。返り値も同じく`CancellationTokenRegistration` です。

```c#
public class NewBehaviourScript : MonoBehaviour
{
    private void Start()
    {
        Observable.Timer(TimeSpan.FromSeconds(10))
                  .Subscribe(_ => Debug.Log("Finish!"))
                  .AddTo(this);
               // .AddTo(destroyCancellationToken); これと同じ
    }
}
```

# `AddTo(ICollection<IDisposable>)`

これはUniRxのときと変わりありません。
R3ではUniRxのときと同じく `CompositeDisposable` が[定義されていて](https://github.com/Cysharp/R3/blob/main/src/R3/CompositeDisposable.cs) これをよく使うことになると思います。

変わらず複数の購読を一括管理するのにとても便利です。

```c#
private NankaClass nanka = new();
private CompositeDisposable disposables = new();

void EventSubscribe()
{
    // CompositeDisposableを生成する
    EventDispose();
    disposables = new();
    
    nanka.OnNankaCompletedPrepare.Subscribe().AddTo(disposables);
    nanka.OnNaknaStart.Subscribe().AddTo(disposables);
    nanka.OnNaknaEnd.Subscribe().AddTo(disposables);
}

void EventDispose()
{
    // AddされたIDisposableを全てDisposeする
    disposables?.Dispose();
}

class NankaClass
{
    private readonly Subject<Unit> onNankaCompletedPrepare = new();
    private readonly Subject<Unit> onNaknaStart = new();
    private readonly Subject<Unit> onNaknaEnd = new();
    public Observable<Unit> OnNankaCompletedPrepare => onNankaCompletedPrepare;
    public Observable<Unit> OnNaknaStart => onNaknaStart;
    public Observable<Unit> OnNaknaEnd => onNaknaEnd;
}
```

# `AddTo(ref DisposableBuilder)`

`DisposableBuilder` は構造体のため、参照渡しをすることになります。
そして、ref構造体なので、通常メンバとして持つことはできません。`DisposableBuilder.Build();`を呼ぶと、**合成されたIDisposable**を取得できます。(渡されたIDisposable群をメンバに持つだけのIDisposable)
こいつを持っておいてDisposeすればまとめて破棄処理ができて便利です。

```c#
private NankaClass nanka = new();
IDisposable disposable;

void EventSubscribe()
{
    EventDispose();

    // DisposableBuilderを生成する
    var disposableBuilder = Disposable.CreateBuilder();
    // var disposables = new DisposableBuilder(); 同じ
    
    nanka.OnNankaCompletedPrepare.Subscribe().AddTo(ref disposableBuilder);
    nanka.OnNaknaStart.Subscribe().AddTo(ref disposableBuilder);
    nanka.OnNaknaEnd.Subscribe().AddTo(ref disposableBuilder);

    disposable = disposableBuilder.Build();
    // disposableBuilder.Dispose(); IDisposableではないが直接Disposeすることもできる
}

void EventDispose()
{
    // AddされたIDisposableを全てDisposeする
    disposable?.Dispose();
}
```

`DisposableBuilder.AddTo(CancellationToken)` は、`Build()`しつつ返り値のIDisposableをCancellationTokenに登録できるショートカットです。
挙動は `disposableBuilder.Build().AddTo(CancellationToken)` と同じです。
```c#
var r = disposableBuilder.AddTo(destroyCancellationToken);
// var r = disposableBuilder.Build().AddTo(destroyCancellationToken); 同じ
```

ちなみに、`disposableBuilder.Build()` をすると、その DisposableBuilder は**その後再度 Add や Build できなくなる**ので注意が必要です。DisposableBuilderはIDisposableではありませんが、その振る舞いはCompositeDisposableに似ています。BuildやDispose後にAdd, Buildしようとすると `ObjectDisposedException`がスローされます

```c#
var disposableBuilder = Disposable.CreateBuilder();
disposableBuilder.Build();
subject.Subscribe().AddTo(ref disposableBuilder); // ObjectDisposedExceptionがスローされる
disposableBuilder.Add(subject); // ObjectDisposedExceptionがスローされる
```

# `AddTo(ref DisposableBag)`

DisposableBag は構造体で、**Remove機能のない簡易的なCompositeDisposable**みたいな感じです。Clearはできます。
これもまとめてDisposeするのに便利です。

```c#
private NankaClass nanka = new();
DisposableBag disposableBag;

void EventSubscribe()
{
    EventDispose();

    // DisposableBagを生成する
    disposableBag = new DisposableBag();
    nanka.OnNankaCompletedPrepare.Subscribe().AddTo(ref disposableBag);
    nanka.OnNaknaStart.Subscribe().AddTo(ref disposableBag);
    nanka.OnNaknaEnd.Subscribe().AddTo(ref disposableBag);
}

void EventDispose()
{
    // AddされたIDisposableを全てDisposeする
    disposableBag.Dispose();
}
```

DisposableBag は IDisposableを実装しているので CompositeDisposable や DisposableBuilder に Addすることもできます。Box化はしますが。

```c#
// CompositeDisposableに追加
compositeDisposable.Add(disposableBag);
disposableBag.AddTo(compositeDisposable);

// DisposableBuilderに追加
disposableBuilder.Add(disposableBag);

// DisposableBagに追加(これはBox化しない)
disposableBag.Add(compositeDisposable);

// DisposableBuilderはIDisposableじゃないのでできない。エラーになる
disposableBag.Add(disposableBuilder);
compositeDisposable.Add(disposableBuilder);
```


# `Disposable.Combine` を使うパターン

`Disposable.Combine`を使えば、AddToを使わず合成を自分で行うこともできます。[公式Readme](https://github.com/Cysharp/R3?tab=readme-ov-file#disposable)によればパフォーマンスも良いらしいです。

> (公式より引用)
five types are available for use. In terms of performance advantages, the order is `Combine(d1,...,d8) (>= CreateBuilder) > Combine(IDisposable[]) >= CreateBuilder > DisposableBag > CompositeDisposable`

```c#
private NankaClass nanka = new();
IDisposable disposable;

void EventSubscribe()
{
    EventDispose();
    
    var d1 = nanka.OnNankaCompletedPrepare.Subscribe();
    var d2 = nanka.OnNaknaStart.Subscribe();
    var d3 = nanka.OnNaknaEnd.Subscribe();

    // Disposable.Combineで複数のIDisposableを合成する
    disposable = Disposable.Combine(d1, d2, d3);
}

void EventDispose()
{
    // AddされたIDisposableを全てDisposeする
    disposable?.Dispose();
}
```

`Disposable.Combine` 引数の数が8個以下かそれ以上かでパフォーマンスが変わります。内部実装を見るとわかりますが、8個まではメンバ変数としてそれぞれ持つのに対し、それ以上は配列として持つような実装になっています。

デメリットとしては、**一度`Combine`したらAddもRemoveもClearもできない** のと、**Combineメソッドへの渡し忘れの注意が必要**なことでしょうか。 

AddToと違って一度ローカル変数を介して合成するので、しっかり全ての購読で返り値を取得して `Disposable.Combine` に入れ忘れないように注意が必要です。

バランスを考えたら個人的には `DisposableBuilder` か 動的に追加したいなら`DisposableBag` がオススメ