Unity オブジェクトのライフサイクルを意識し MissingReferenceException を撲滅しよう 〜Destroyされた変数の中身は何なのか〜

Unity で開発していると ***MissingReferenceException*** に悩まされることが多々あると思います。再現性が低いととても厄介です。これは、Unity オブジェクトのライフサイクルを意識することで抑制できます。
今回は ***MissingReferenceException*** が発生しやすい 3つのケースと、***MissingReferenceException*** を発生させる Destroy されたオブジェクトの正体を考察してみます。

# Case. 1

```csharp
public class Test : MonoBehaviour {

    public class InternalScript : MonoBehaviour {}

    IEnumerator Start() {
        var go = new GameObject("InternalObject");
        var script = go.AddComponent<InternalScript>();
        Debug.Log("script.enabled: " + script.enabled);
        GameObject.Destroy(go);
        Debug.Log("script.enabled: " + script.enabled);
        yield return null;
        Debug.Log("script.enabled: " + script.enabled);
    }
}
```

```
script.enabled: True
script.enabled: False
MissingReferenceException: The object of type 'InternalScript' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
```
最もよく見るケースで、３回目の `script.enabled` で ***MissingReferenceException*** が発生しています。GameObject を Destroy すると、参照が残っていようが次のフレームにはアタッチされている Component インスタンスも含め、全てのオブジェクトが破棄されます。
エラーメッセージには「null チェックするか Destroy するなよ」とありますので、メンバ変数にアクセスする前に、null チェックを入れることで解決できます。
比較的意識しやすく、対処もしやすいので、あまり大きな問題にはならない。


# Case. 2

```csharp
public class Test : MonoBehaviour {

    public class InternalScript : MonoBehaviour {
        public IEnumerator Run() {
            while (true) {
                Debug.Log("this: " + this);
                Debug.Log("this.enabled: " + this.enabled);
                yield return new WaitForSeconds(1.0f);
            }
        }
    }

    IEnumerator Start() {
        var go = new GameObject("InternalObject");
        var script = go.AddComponent<InternalScript>();
        StartCoroutine(script.Run());
        GameObject.Destroy(go);
    }
}
```

```
this: InternalObject (InternalScript)
this.enabled: True
this: null
MissingReferenceException: The object of type 'InternalScript' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
```

こちらは、2回目の `this.enabled` で ***MissingReferenceException*** が発生しています。 `Run` 関数の中では自分自身のメンバにアクセスしているので意識されにくいですが、 `this` 自体が null を返しているのが分かります。
このケースでは `StartCoroutine` を実行する際に、コルーチンを実行するオブジェクトが適切かを意識しなければなりません。
上記のコードでは、自身を参照する `InternalScript` のコルーチンを `Test` オブジェクトで実行しているため、 `InternalScript` オブジェクトが Destroy されても `Test` オブジェクトはコルーチンを実行し続けます。

`InternalScript` を下記のように実装するのが、間違った使い方をされずにすむでしょう。

```csharp
public class Test : MonoBehaviour {

    public class InternalScript : MonoBehaviour {
        public void Run() {
            StartCoroutine(InternalRun());
        }

        IEnumerator InternalRun() {
            while (true) {
                Debug.Log("this: " + this);
                Debug.Log("this.enabled: " + this.enabled);
                yield return new WaitForSeconds(1.0f);
            }
        }
    }

    IEnumerator Start() {
        var go = new GameObject("InternalObject");
        var script = go.AddComponent<InternalScript>();
        script.Run();
        GameObject.Destroy(go);
    }
}
```

# Case. 3

```csharp
public class Test : MonoBehaviour {

    public class InternalScript : MonoBehaviour {
        public void Call() {
            Debug.Log("this.enabled: " + this.enabled);
        }
    }

    IEnumerator Start() {
        var go = new GameObject("InternalObject");
        var script = go.AddComponent<InternalScript>();
        var func = new Action(script.Call);
        func();
        GameObject.Destroy(go);
        func();
        yield return null;
        func();
    }
}
```

```
script.enabled: True
script.enabled: False
MissingReferenceException: The object of type 'InternalScript' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
```

このケースでは、３回目の `this.enabled` で ***MissingReferenceException*** が発生しています。
これを防ぐには呼び出し側で Delegate のインスタンスが生きているかを次のように確認します。

```csharp
public class Test : MonoBehaviour {

    public class InternalScript : MonoBehaviour {
        public void Call() {
            Debug.Log("this.enabled: " + this.enabled);
        }
    }

    public class Event {
        Action callback;

        public Event(Action callback) {
            this.callback = callback;
        }

        public void Call() {
            var component = callback.Target as MonoBehaviour;
            if (component != null)
                callback();
        }
    }

    IEnumerator Start() {
        var go = new GameObject("InternalObject");
        var script = go.AddComponent<InternalScript>();
        var ev = new Event(script.Call);
        ev.Call();
        GameObject.Destroy(go);
        ev.Call();
        yield return null;
        ev.Call();
    }
}
```

`Delegate.Target` には、その Delegate のインスタンスオブジェクトが参照されているので、こいつを null チェックします。
ポイントは ***MonoBehaviour*** にキャストした上で null チェックを行うことです。
理由は次の項で説明します。

このままでは無名関数を Delegate として渡しても絶対実行されないので `Event` を無名関数でも使えるようにする為には次のようにします。

```csharp
public class Event {
    Action callback;
    MonoBehaviour instance;
    bool isComponent;

    public Event(Action callback) {
        this.callback = callback;
        this.instance = callback.Target as MonoBehaviour;
        this.isComponent = instance != null;
    }

    public void Call() {
        if (!isComponent || instance != null)
            callback();
    }
}
```


# Destroy された変数の中身は null ではない

*※ここからは私の考察であり、実際の実装とは異なる場合があります。この辺りの公開情報をご存じであれば、ご教授頂けると幸いです。*

ここまでは、Destroy されたかどうかを null チェックで判断してきたが、Destory されたオブジェクトは本当に null なのでしょうか。
次のコードを見てみましょう。

```csharp
public class Test : MonoBehaviour {

    public class InternalScript : MonoBehaviour {
        public string whoami = "I am an InternalScript.";
    }

    IEnumerator Start() {
        var go = new GameObject("InternalObject");
        var script = go.AddComponent<InternalScript>();
        GameObject.Destroy(go);
        yield return null;
        Debug.Log("script: " + script);
        Debug.Log("script.whoami: " + script.whoami);
        Debug.Log("script.enabled: " + script.enabled);
    }
}
```

```
script: null
script.whoami: I am an InternalScript.
MissingReferenceException: The object of type 'InternalScript' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
```

`script` インスタンスは null を返しているが、`script.whoami` は正常にアクセスできています。しかし、次の `script.enabled` は ***MissingReferenceException*** が発生しています。
この ***MissingReferenceException*** は Unity で定義されている例外で C# の null参照エラーは ***NullReferenceException*** です。これは、Destory されたオブジェクトは null のように振る舞うだけで実際には残っており、継承されたメンバにアクセスした際に ***MissingReferenceException*** を throw していることになります。

### では何が Destory されたのか。

Unity には２系統のメモリマネージャ(GC)が存在します。mono と Unity です。mono は C# 上のマネージドメモリを管理し、Unity はネイティブコードのメモリを管理します。（C/C++ で new/delete されているようなもの。）

C# コード上で、Unity オブジェクトを生成した際、mono のメモリ上で変数が生成されると同時に、ネイティブコード上にもメモリが確保（こちらが実態）され、C# 上のオブジェクトはネイティブコード上のインスタンスを参照します。

Destory されると、ネイティブ側のメモリが破棄され、C# 側のネイティブオブジェクトへの参照が失われると、null として振る舞うようになり、ネイティブオブジェクトを参照していたメンバにアクセスされると ***MissingReferenceException*** を throw します。

null としての振る舞いは `ToString(...)` や `== operator` をオーバーライドすることで表現されています。
`(object)component == null` や `component ?? go.AddComponent<..>()` が意図した動作を行わない原因です。

ネイティブコードへの参照を失った C# 側のオブジェクトは mono のメモリに残り続け、mono の GC スケジュールにより破棄されます。