LoginSignup
12
8

More than 5 years have passed since last update.

[C#,Unity]usingステートメントを使っても必ずDisposeされるとは限らない

Last updated at Posted at 2019-03-27

基本的にusingステートメントを使用するとブロックの終了時にDisposeが呼ばれます。

サンプル
class Disposable : IDisposable
{
    public void Dispose()
    {
        Debug.Log("Dispose");
    }
}

private void Hoge()
{
    using (new Disposable())
    {
        Debug.Log(1);
        Debug.Log(2);
    }
}

上記を実行すると、12Disposeと表示されます。
ただし、yieldasync/awaitを使用する状況ではDisopseが呼ばれない可能性があります。
例えば以下のようなケースです。

void Start()
{
    // コルーチンの実行
    StartCoroutine(Fuga());
}

private IEnumerator Fuga()
{
    using (new Disposable())
    {
        Debug.Log(1);
        yield return null;
        Debug.Log(2);
        // コルーチンが終了する前にゲームオブジェクトを削除
        Destroy(gameObject);
        yield return null;
        Debug.Log(3);
    }
}

この場合、12までしか表示されません。
UnityがIEnumeratorの列挙を途中でやめてしまうからです。

var enumerator = Fuga();
enumerator.MoveNext();
enumerator.MoveNext();
// ここで列挙が終了する。
// 3回目のMoveNextを呼べばDisposeされるがGameObjectが削除されているので呼ばれない。
// enumerator.MoveNext()
// もしくはenumeratorがDisposeされるならばDisposeが呼ばれる。
// if (enumerator is IDisposable d) d.Dispose();

usingステートメントだけではなくtry/finallyも同様です。

コルーチンをやめてUniRx.Asyncを使用すればこんなことは起こりません。

void Start()
{
    Fuga(this.GetCancellationTokenOnDestroy()).Forget();
}

private async UniTask Fuga(CancellationToken token)
{
    using (new Disposable())
    {
        Debug.Log(1);
        await UniTask.Yield(PlayerLoopTiming.Update, token);
        Debug.Log(2);
        Destroy(gameObject);
        await UniTask.Yield(PlayerLoopTiming.Update, token);
        Debug.Log(3);
    }
}

上記を実行すると、12Disposeと表示されます。
これはUniRx.Asyncオブジェクトの寿命とは別にタスクを実行していること、
キャンセルがリクエストされたことでOperationCanceledExceptionが発生しているからです。
(逆に言うとトークンを正しく渡さない限りオブジェクトが削除された後でも先に進んでしまいます。)

12
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
8