Unityのnullはnullじゃないかもしれない

という話。
以前、友人に上手く説明できなかったので、自分なりに色々検証コードを書いてみました。
リポジトリはこちら https://github.com/satanabe1/UnityNullObjects

---

### nullチェク、しますよね。
`if (obj == null) return;`
よくあるパターンです。
Unityで `== null` が成り立つ状況って、ざっくり次の二つが挙げられます。

1. もともとnullが代入されていた (nullでないものが代入されなかった)
2. Destroyされた

当たり前なことしか書いてないですが、この2つ目が厄介です。
以下にぐだぐだと書いていきます。

#### Destroyされてもすぐにはnullにならない

```csharp
/// <summary>
/// DestroyとDestroyImmediate
/// </summary>
public class NullTest01 : NullTestBase
{
    IEnumerator Start ()
    {
        MonoBehaviour destroied = gameObject.AddComponent<MonoBehaviour> ();
        MonoBehaviour destroiedImmediate = gameObject.AddComponent<MonoBehaviour> ();
        MonoBehaviour nullBehaviour = null;
        Destroy (destroied);
        DestroyImmediate (destroiedImmediate);
        Debug.Log ("destroied          : [" + destroied + "]");
        Debug.Log ("destroiedImmediate : [" + destroiedImmediate + "]");
        Debug.Log ("nullBehaviour      : [" + nullBehaviour + "]");
        yield break;
    }
}

/****** ログ *****
destroied : [Runner (UnityEngine.MonoBehaviour)]
destroiedImmediate : [null]
nullBehaviour : [] 
***** ******/
```
ログの一行目、Destroy済みの`destroied`が、`destroied : [Runner (UnityEngine.MonoBehaviour)]` と出てますね。
`DestroyImmediate`された方は綺麗さっぱり `null` になってますね。
まぁ、Destroyしてもすぐに消えるわけじゃないぜ。っと公式ドキュメントにも書いてあった気がします。納得です。

#### Destroyからの1フレーム後ならちゃんとnull (?)
```csharp
/// <summary>
/// DestroyとDestroyImmediateとフレーム跨ぎ
/// </summary>
public class NullTest02 : NullTestBase
{
    IEnumerator Start ()
    {
        MonoBehaviour destroied = gameObject.AddComponent<MonoBehaviour> ();
        MonoBehaviour destroiedImmediate = gameObject.AddComponent<MonoBehaviour> ();
        MonoBehaviour nullBehaviour = null;
        Destroy (destroied);
        DestroyImmediate (destroiedImmediate);
        yield return null;
        Debug.Log ("destroied          : [" + destroied + "]");
        Debug.Log ("destroiedImmediate : [" + destroiedImmediate + "]");
        Debug.Log ("nullBehaviour      : [" + nullBehaviour + "]");
        yield break;
    }
}
/****** ログ *****
destroied          : [null]
destroiedImmediate : [null]
nullBehaviour      : []
***** ******/
```
Debug.Logの前に`yield return null;`を挟んだだけです。
直感に沿う結果です。
ただ、そろそろ気持ち悪さを感じますね。
_なぜ、唯一最初からnullを代入されていた`nullBehavior`だけが違う出力なのか。_

#### なら '== null'してみよう
```csharp
/// <summary>
/// 削除済みobjectとnull
/// </summary>
public class NullTest03 : NullTestBase
{
    IEnumerator Start ()
    {
        MonoBehaviour destroied = gameObject.AddComponent<MonoBehaviour> ();
        MonoBehaviour destroiedImmediate = gameObject.AddComponent<MonoBehaviour> ();
        MonoBehaviour nullBehaviour = null;
        Destroy (destroied);
        DestroyImmediate (destroiedImmediate);
        Debug.Log ("EqualsNull");
        Debug.Log ("destroied          : [" + (destroied == null) + "]");
        Debug.Log ("destroiedImmediate : [" + (destroiedImmediate == null) + "]");
        Debug.Log ("nullBehaviour      : [" + (nullBehaviour == null) + "]");
        yield return null;
        Debug.Log ("destroied          : [" + (destroied == null) + "]");
        Debug.Log ("destroiedImmediate : [" + (destroiedImmediate == null) + "]");
        Debug.Log ("nullBehaviour      : [" + (nullBehaviour == null) + "]");
        yield break;
    }
}
/****** ログ *****
EqualsNull
destroied          : [False]
destroiedImmediate : [True]
nullBehaviour      : [True]
destroied          : [True]
destroiedImmediate : [True]
nullBehaviour      : [True]
***** ******/
```
削除が完了すれば、ちゃんと`== null`が成り立ってますね。
やっぱりみんなnullじゃん。
じゃあ、なんで出力が違ったの？ということで、

#### nullなオブジェクトにメソッド(GetTypeとGetComponentInParent)呼び出ししてみる
```csharp
/// <summary>
/// 削除済みobjectへのメソッド呼び出し
/// </summary>
public class NullTest04 : NullTestBase
{
    IEnumerator Start ()
    {
        MonoBehaviour destroied = gameObject.AddComponent<MonoBehaviour> ();
        MonoBehaviour destroiedImmediate = gameObject.AddComponent<MonoBehaviour> ();
        MonoBehaviour nullBehaviour = null;
        Destroy (destroied);
        DestroyImmediate (destroiedImmediate);
        yield return null;
        Debug.Log ("=====  GetType  =====");
        try {
            Debug.Log ("---");
            Debug.Log ("destroied          : [" + destroied.GetType () + "]");
        } catch (System.Exception ex) {
            Debug.LogError (ex);
        }
        try {
            Debug.Log ("---");
            Debug.Log ("destroiedImmediate : [" + destroiedImmediate.GetType () + "]");
        } catch (System.Exception ex) {
            Debug.LogError (ex);
        }
        try {
            Debug.Log ("---");
            Debug.Log ("nullBehaviour      : [" + nullBehaviour.GetType () + "]");
        } catch (System.Exception ex) {
            Debug.LogError (ex);
        }
        Debug.Log ("=====  GetComponentInParent  =====");
        try {
            Debug.Log ("---");
            Debug.Log ("destroied          : [" + destroied.GetComponentInParent<Component> () + "]");
        } catch (System.Exception  nex) {
            Debug.LogError (nex);
        }
        try {
            Debug.Log ("---");
            Debug.Log ("destroiedImmediate : [" + destroiedImmediate.GetComponentInParent<Component> () + "]");
        } catch (System.Exception ex) {
            Debug.LogError (ex);
        }
        try {
            Debug.Log ("---");
            Debug.Log ("nullBehaviour      : [" + nullBehaviour.GetComponentInParent<Component> () + "]");
        } catch (System.Exception ex) {
            Debug.LogError (ex);
        }
        yield break;
    }
}
/****** ログ *****
=====  GetType  =====
---
destroied          : [UnityEngine.MonoBehaviour]
---
destroiedImmediate : [UnityEngine.MonoBehaviour]
---
System.NullReferenceException: Object reference not set to an instance of an object
  at NullTest04+<Start>c__Iterator3.MoveNext () [0x0011d] in /Users/hoge/unity/Null/Assets/NullTest04.cs:32 
=====  GetComponentInParent  =====
---
UnityEngine.MissingReferenceException: The object of type 'MonoBehaviour' 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.
  at (wrapper managed-to-native) UnityEngine.Component:InternalGetGameObject ()
  at UnityEngine.Component.get_gameObject () [0x00000] in <filename unknown>:0 
  at UnityEngine.Component.GetComponentInParent (System.Type t) [0x00000] in <filename unknown>:0 
  at UnityEngine.Component.GetComponentInParent[Component] () [0x00000] in <filename unknown>:0 
  at NullTest04+<Start>c__Iterator3.MoveNext () [0x0016d] in /Users/hoge/unity/Null/Assets/NullTest04.cs:39 
---
UnityEngine.MissingReferenceException: The object of type 'MonoBehaviour' 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.
  at (wrapper managed-to-native) UnityEngine.Component:InternalGetGameObject ()
  at UnityEngine.Component.get_gameObject () [0x00000] in <filename unknown>:0 
  at UnityEngine.Component.GetComponentInParent (System.Type t) [0x00000] in <filename unknown>:0 
  at UnityEngine.Component.GetComponentInParent[Component] () [0x00000] in <filename unknown>:0 
  at NullTest04+<Start>c__Iterator3.MoveNext () [0x001b5] in /Users/hoge/unity/Null/Assets/NullTest04.cs:45 
---
System.NullReferenceException: Object reference not set to an instance of an object
  at NullTest04+<Start>c__Iterator3.MoveNext () [0x001fd] in /Users/hoge/unity/Null/Assets/NullTest04.cs:51 
***** ******/
```
##### GetType
そう。Destroyされてnullになったはずの`destroied`と`destroiedImmediate`から`GetType()`で型を拾えてしまうんです。
最初からnullを代入されていた`nullBehaviour`はNullReferenceException。

##### GetComponentInParent
一方、GetComponentInParentだと、3つともエラーになってしまうものの・・・やっぱり様子が違いますね。
やっぱり、こいつら純粋なnullじゃない？

#### is a してみる
```csharp
MonoBehaviour nullBehaviour = null;
Destroy (destroied);
DestroyImmediate (destroiedImmediate);
yield return null;
Debug.Log("is a UnityObject");
Debug.Log ("destroied          : [" + (destroied is UnityEngine.Object) + "]");
Debug.Log ("destroiedImmediate : [" + (destroiedImmediate is UnityEngine.Object) + "]");
Debug.Log ("nullBehaviour      : [" + (nullBehaviour is UnityEngine.Object) + "]");
/****** ログ *****
is a UnityObject
destroied          : [True]
destroiedImmediate : [True]
nullBehaviour      : [False]
***** ******/
```
やはり`nullBehaviour`だけ仲間はずれです。
一見、nullと等価であり、かつ、UnityEngine.Objectでもある。そしてNullReferenceではない。
という動きですね。

#### どうしてこうなるのか？
![スクリーンショット 2015-01-02 19.03.29.png](https://qiita-image-store.s3.amazonaws.com/0/5973/ba6bc7a5-8f84-b68d-2855-f96ee2c31b64.png)
UnityEngine.Objectの定義をMonoDevelopで追ってみると、`==`演算子がオーバーライドされている事がわかります。
`Object.CompareBaseObjects`メソッドの中身までは追っていませんが、おそらくDestroy済みかどうかのフラグ的なものをみて、Destroy済みならnullに対してtrueを返す・・・といった処理が書いてあるのでしょう。きっと。

したがって、 _Unityオブジェクトの場合、'== null'が成り立っても本当にnullである保証はどこにもない_ のです。
つまり、 _メモリは開放されていない_

### 仮にnullがnullじゃなかったとして、何がいけないのでしょうか？
と言われてしまいそうですが、これ、意識の片隅に置いておいたほうがいいかもしれません。

たとえば、`== null`の親戚に `??`演算子があります。
'左辺がnullなら右辺を返す' という意味の演算子ですね。
この`??`演算子はUnityオブジェクト側でオーバーライドされていないので、破棄済みのUnityオブジェクトにたいしても、'nullではない' として扱います。事故ですね。

また、nullチェックが成り立ったからといって、そのまま変数を放置しておいたらメモリ中にUnityオブジェクトの死体が散乱するかもしれません。地獄ですね。

そしてもう一点、UnityオブジェクトをSystem.Objectにアップキャストしてからnullチェックを行うと、`== null`が牙を剝いてきます。(僕はそれでヤラレました)
下記のコードと、そのログ出力をよく眺めて見て下さい。

```csharp
Destroy (destroied);
DestroyImmediate (destroiedImmediate);
yield return null;
Debug.Log ("EqualsNull");
Debug.Log ("destroied                 : [" + (destroied == null) + "]");
Debug.Log ("destroiedImmediate        : [" + (destroiedImmediate == null) + "]");
Debug.Log ("nullBehaviour             : [" + (nullBehaviour == null) + "]");
Debug.Log ("(cast) destroied          : [" + (((System.Object)destroied) == null) + "]");
Debug.Log ("(cast) destroiedImmediate : [" + (((System.Object)destroiedImmediate) == null) + "]");
Debug.Log ("(cast) nullBehaviour      : [" + (((System.Object)nullBehaviour) == null) + "]");
/****** ログ *****
EqualsNull
destroied                 : [True]
destroiedImmediate        : [True]
nullBehaviour             : [True]
(cast) destroied          : [False]
(cast) destroiedImmediate : [False]
(cast) nullBehaviour      : [True]
***** ******/
```
これはUnity以前のC#の振る舞いですが、キャストしたことによって、`==`演算子のオーバーライドが効かなくなっています。
よって、たとえば、

```csharp
/// nullだっときの汎用的な処理が欲しい。とか。
bool IsNull(System.Object arg)
{
   Debug.Log("nullだよ。とかログを取ったり。ごにょったり。");
   return arg == null;
}
```
のようなコードを書いていたら、Destroy済みのUnityオブジェクトであっても'nullではない'という結果が返って来ます。MissingReferenceExceptionさんこんにちは。

### nullがnullでないnullであることをnullチェックするには
おまけです。

```csharp
System.Object obj = ...
if (obj is UnityEngine.Object) {
    if ((UnityEngine.Object)obj != null) {
        // 元気なUnityオブジェクト
    } else {
        // 死んだフリしているUnityオブジェクト
    }
} else {
    if (obj != null) {
        // 普通のnullでないオブジェクト
    } else {
        // ガチnull
    }
}
```
こんな感じでしょうか。
まともじゃないですね。

そもそもSystem.Objectを使わない、というのがUnityのお作法かもしれません。