【Unity】破棄を忘れずに行うためにIDisposableをうまく利用する方法【UniRx】

# はじめに
「クラス定義の上のほうにインスタンス変数をたくさん書いて`OnDestroy()`内ですべて開放する処理を書く」みたいなことをしていると、どうしてもリソースの破棄のし忘れが発生してしまいます。

この記事では`IDisposable`インタフェースに関連するクラスや仕組みとその使い方についてまとめ、忘れずに破棄を行うにはどうしたらいいかについて述べます。

UnityのデフォルトのC#で使える`using`ステートメントだけでなく、UniRxの`CompositeDisposable`クラスや`AddTo(GameObject gameObject)`メソッドについてもご紹介するので、UniRxを使っていない方はぜひ導入してみてください。

* [GitHub - UniRx](https://github.com/neuecc/UniRx)
* [UnityAssetStore - UniRx](http://u3d.as/7tT)


# `IDisposable`インターフェース
[MSDNの説明](https://msdn.microsoft.com/ja-jp/library/system.idisposable(v=vs.110).aspx)によると、

> アンマネージ リソースを解放するためのメカニズムを提供します。

となっています。実装すべきインターフェース自体は`void Dispose()`のみといたってシンプルです。

`System`名前空間にあるので`using System;`を忘れずに。

# 解説の準備
解説のためにインスタンス生成時と`Dispose()`が呼ばれたときにログを吐く`IDisposable`インターフェース実装クラスを作ります。

```DisposeLog.cs
using System;
using UnityEngine;

public class DisposeLog : IDisposable
{
    public string id { get; private set; }

    public DisposeLog (string id)
    {
        this.id = id;
        Debug.Log(string.Format("{0}:constructed", this.id));
    }

    public void Dispose()
    {
        Debug.Log(string.Format("{0}:called Dispose()", this.id));
    }
}
```

# 便利な道具たち

## `using`ステートメント
usingステートメントを使うと、ブロックを抜けたときに`()`に与えたオブジェクトの`Dispose()`が**<font color=red>必ず</font>**呼ばれます。

```DisposableSample1.cs
using System;
using System.Collections;
using UnityEngine;

public class DisposableSample1 : MonoBehaviour
{
    void Start()
    {
        try {
            NormalCase();
            ReturnCase();
            ErrorCase();
        } catch {
        }

        StartCoroutine(CoroutineCase1());
        StartCoroutine(CoroutineCase2());
        StartCoroutine(CoroutineCase3());
    }

    private void NormalCase()
    {
        using (var disposable = new DisposeLog("1")) {
            // 特に何もしない
        }
    }

    private void ReturnCase()
    {
        using (var disposable = new DisposeLog("2")) {
            return;
        }
    }

    private void ErrorCase()
    {
        using (var disposable = new DisposeLog("3")) {
            throw new Exception("error");
        }
    }

    private IEnumerator CoroutineCase1()
    {
        using (var disposable = new DisposeLog("4")) {
            // 特に何もしない
        }
        yield return null;
    }

    private IEnumerator CoroutineCase2()
    {
        using (var disposable = new DisposeLog("5")) {
            yield break;
        }
    }

    private IEnumerator CoroutineCase3()
    {
        using (var disposable = new DisposeLog("6")) {
            throw new Exception("error in coroutine");
        }
        yield return null;
    }
}
```
```
実行結果
1:constructed
1:called Dispose()
2:constructed
2:called Dispose()
3:constructed
3:called Dispose()
4:constructed
4:called Dispose()
5:constructed
5:called Dispose()
6:constructed
6:called Dispose()
Exception: error in coroutine
```
上記のサンプルコードでは`yield break`で途中で処理を抜けたり`using`ブロックの内部でエラーを発生させていますが、ちゃんと`Dispose()`が呼ばれていることが確認できます。

これはコンパイラがコードを内部的に`try~finally`に変換してくれるからです（[MSDNの解説](https://msdn.microsoft.com/ja-jp/library/yh598w02.aspx)）。

ちなみに`()`で与えたオブジェクトは`using`ブロック内では読み取り専用になります。

## `CompositeDisposable`クラス
UniRxには`CompositeDisposable`というクラスが含まれています。このクラスは`ICollection<IDisposable>`を実装していて、`IDisposable`をまとめて扱うのに便利です。

```DisposableSample2.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UniRx;

public class DisposableSample2 : MonoBehaviour
{
    private List<IDisposable> _disposableList = new List<IDisposable>();
    private CompositeDisposable _compositeDisposable = new CompositeDisposable();

    void Start()
    {
        // List
        var disposable1 = new DisposeLog("1");
        _disposableList.Add(disposable1);
        _disposableList.Add(new DisposeLog("2"));

        // CompositeDisposable
        var disposable3 = new DisposeLog("3");
        _compositeDisposable.Add(disposable3);
        _compositeDisposable.Add(new DisposeLog("4"));
    }

    void OnDestroy()
    {
        // List<IDisposable>の破棄
        foreach (var disposable in _disposableList) {
            disposable.Dispose();
        }

        // CompositeDisposableの破棄、Clear()とDispose()は挙動が異なる
        _compositeDisposable.Clear();
    }
}
```

サンプルコードでは比較のために`List<IDisposable>`を使った実装もしてあります。`CompositeDisposable`を使ったほうが`OnDestroy()`内の破棄でforeachを使っておらず、若干スッキリしているのがわかると思います。

`CompositeDisposable`はAddされた`IDisposable`をRemoveするときに`Dispose()`を呼びます。`Clear()`メソッドはすべてをRemoveして`Dispose()`してくれます。

Addした`IDisposable`を一度に`Dispose()`するメソッドは`Clear()`と`Dispose()`があるのですが、`Dispose()`を呼び出すとそれ以降はAddした`IDisposable`を即`Dispose()`するようになるので、`CompositeDisposable`のインスタンス自体を使いまわしたい場合は`Clear()`を使うほうがいいと思います。

## `AddTo`メソッド
UniRxを利用することで`IDisposable`インターフェースに`AddTo`というメソッドが追加されます。オーバーロードがいくつかありますが、以下のような挙動となっています。

いずれも戻り値は`IDisposable`です。

| シグネチャ | 挙動 |
|:----|:----|
| `AddTo(GameObject gameObject)` | GameObjectの`OnDestroy()`のタイミングでレシーバの`Dispose()`が呼ばれる |
| `AddTo(Component gameObjectComponent)` | `component.gameObject`の`OnDestroy()`のタイミングでレシーバの`Dispose()`が呼ばれる |
| `AddTo(ICollection<IDisposable> container)` | `container.Dispose()`のタイミングでレシーバの`Dispose()`が呼ばれる（内部的に`container.Add(レシーバ)`される） |
| `AddTo(ICollection<IDisposable> container, GameObject gameObject)` | `container.Dispose()`とGameObjectの`OnDestroy()`のタイミングでレシーバの`Dispose()`が呼ばれる（内部的には`AddTo(container).AddTo(gameObject)`している） |


上記の表をみてもピンと来にくいのでサンプルコードを載せておきます。

```Sample3.cs
using UnityEngine;
using UniRx;

public class DisposableSample3 : MonoBehaviour
{
    CompositeDisposable _compositeDisposable = new CompositeDisposable();

    void Start()
    {
        // OnDestroy()での破棄
        new DisposeLog("1").AddTo(this);
        new DisposeLog("2").AddTo(this.gameObject);

        // OnClick()での破棄
        new DisposeLog("3").AddTo(_compositeDisposable);
        new DisposeLog("4").AddTo(_compositeDisposable, this.gameObject);
    }

    public void OnClick()
    {
        _compositeDisposable.Clear();
    }
}
```

簡単に言うと、「AddToに渡した引数の破棄タイミングで破棄をするように予約を入れる」感じです。リストに加えるときとは主語と述語が反対になっており、メソッドチェーンで書けてスッキリしました。

1,2は自分に紐づく`GameObject`の`OnDestroy()`のタイミングでの破棄の予約をしています。実際は自動で`AddComponent`される`ObservableDestroyTrigger`というコンポーネントの`OnDestroy()`内で`Dispose()`が呼ばれます。

3,4は`OnClick()`が呼ばれるタイミングで破棄されるようにしてみました。このように破棄タイミングを自分でコントロールしたい場合は`CompositeDisposable`を使いましょう。
4については`OnDestroy()`と`OnClick()`の両方のタイミングで破棄されます。

### `AddTo(this)`と`AddTo(this.gameObject)`
UniRx関係のコードで`AddTo(this)`と`AddTo(this.gameObject)`の両方のコードを見かけますが、細かい挙動の違いはあれど`AddTo(this.gameObject)`よりは`AddTo(this)`のほうが短く書けていいと思います。


## `Disposable.Create`
UniRxの`Disposable.Create(Action disposeAction)`を使うと簡単に`IDisposable`インターフェースを実装したインスタンスを作成できます。シグネチャを見ればだいたい察しがつく通り、`Dispose()`が呼ばれると引数で渡した`Action disposeAction`が実行されます。

```DisposableSample4.cs
using UnityEngine;
using UniRx;

public class DisposableSample4 : MonoBehaviour
{
    void Start()
    {
        var notDisposable = new NotDisposable(1, "hoge");

        // 破棄処理の登録
        Disposable.Create(() => {
            notDisposable.id = 0;
            notDisposable.name = "";
        }).AddTo(this);

        var disposable = Disposable.Create(() => notDisposable.name = "disposed");
        using (disposable) {
            // 何らかの処理
        }
        Debug.Log(notDisposable.name); // -> "disposed"
    }

    public class NotDisposable
    {
        public int id{ get; set; }

        public string name{ get; set; }

        public NotDisposable (int id, string name)
        {
            this.id = id;
            this.name = name;
        }
    }
}
```

「`using`ブロックや`CompositeDisposable`などを使いたいが、いちいち`IDisposable`を実装したクラスを作るほどでもない」というときに役立ちます。

一番有用な使い方はサンプルコードのように`IDisposable`を実装していないリソースについて確保と破棄を同じ場所に書いておけることだと思います。

## `Disposable.Empty`
UniRxの`Disposable.Empty`は`Dispose()`が呼ばれても何もしない`IDisposable`を返します。
「引数で`IDisposable`が必要だけど特になにもする必要がない」という場合に使えます。

# 忘れずに破棄を行うにはどうしたらいいか
そもそも破棄をし忘れるのは「リソースの確保と破棄の処理が離れている」ことが問題であることがほとんどです。

なので、

* 破棄処理を忘れてもいいようにしておく
* リソースの確保と破棄をセットで書く

のが肝心です。

具体的には以下をなるべく守るようにしましょう。

* なるべくすべてのクラスで`IDisposable`を実装する
* できない場合は`Disposable.Create`をうまく使う
* インスタンスを使うスコープが限定されているなら`using`ステートメントを使う
* リソースの確保処理と同時に破棄を登録する処理を書く
  * `OnDestroy()`で破棄を行うなら`AddTo(this)`
  * タイミングを細かく制御したいなら`CompositeDisposable`を使う

## 番外編：`using`ブロックとコルーチン
（後ほど）