基本的にusingステートメントを使用するとブロックの終了時にDisposeが呼ばれます。
サンプル
class Disposable : IDisposable
{
public void Dispose()
{
Debug.Log("Dispose");
}
}
private void Hoge()
{
using (new Disposable())
{
Debug.Log(1);
Debug.Log(2);
}
}
上記を実行すると、1
、2
、Dispose
と表示されます。
ただし、yieldやasync/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);
}
}
この場合、1
、2
までしか表示されません。
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);
}
}
上記を実行すると、1
、2
、Dispose
と表示されます。
これはUniRx.Asyncがオブジェクトの寿命とは別にタスクを実行していること、
キャンセルがリクエストされたことでOperationCanceledExceptionが発生しているからです。
(逆に言うとトークンを正しく渡さない限りオブジェクトが削除された後でも先に進んでしまいます。)