【Unity】【C#】UnityEngine.Objectのnullチェックで?演算子とか??演算子は使わないほうがよさそうだ

Unityのオブジェクトに?演算子とか??演算子は使わないほうがよさそうだという話です。

Unity2018.4.0

問題点

UnityでC#6を使えるようになってからnull条件演算子を使ってオプショナルチェイン的な書き方ができるようになりました。
これにより例えばこんな感じで頑張ってif文を書いていたのを、

using UnityEngine;

public class Example : MonoBehaviour
{
    [SerializeField]
    private Renderer _renderer;

    void Start()
    {
        string name = null;
        if (_renderer != null && _renderer.material != null) {
            name = _renderer.material.name;
        }
        Debug.Log(name);
    }
}

下記のような記述でスマートに書けるようになるはずでした。

using UnityEngine;

public class Example : MonoBehaviour
{
    [SerializeField]
    private Renderer _renderer;

    void Start()
    {
        Debug.Log(_renderer?.material?.name);
    }
}

この書き方で_rendererとか_renderer.materialとかがnullだったとしたらその時点で後続処理はスキップされてnullが返ってくるはずです。

が、実際に動かしてみると上記のソースコードはうまく動きません。
_rendererに何もアサインしていない状態で再生すると、UnassignedReferenceExceptionがスローされてしまいます。

f:id:halya_11:20190901220553p:plain

また、次のようにUnityEngine.Objectを破棄した時にもこの問題は起こります。

using UnityEngine;

public class Example : MonoBehaviour
{
    void Start()
    {
        Material material = new Material(Shader.Find("Standard"));

        // RGBA(1.000, 1.000, 1.000, 1.000)
        Debug.Log(material?.color);

        DestroyImmediate(material);

        // MissingReferenceException
        Debug.Log(material?.color);
    }
}

コメントの通り、Materialを破棄した後にmaterial?.colorにアクセスするとnullではなくMissingReferenceExceptionがスローされます。

f:id:halya_11:20190901222345p:plain

さらにこのような問題はnull合体演算子でも起こります。

using UnityEngine;

public class Example : MonoBehaviour
{
    [SerializeField]
    private Renderer _renderer;

    void Start()
    {
        var dummy = gameObject.AddComponent<MeshRenderer>();
        dummy.name = "Dummy";
        Debug.Log(_renderer ?? dummy);
    }
}

上記のコードでは_rendererが無かったらDummyが表示されることが期待できますが、
実際に_rendererを外して再生すると_rendererが参照されてnullと表示されてしまいます。

原因と対応策

これはUnityEngine.Objectが==演算子オーバーロードしていることが原因のようです。

オーバーロードしている理由としては破棄したオブジェクトもnullとして扱ってしまえるようにするためですが、
?演算子や??演算子ではこのオーバーロードを使わないためおかしな挙動になります。

対応としては、「UnityEngine.Objectを継承したクラスではこれらの演算子を使うな」ということになるようです。。

issuetracker.unity3d.com

残念な話ですが、仕方ないですね。
Unity内でもどうするべきか議論はされているようです。

https://forum.unity.com/threads/elvis-operator-null-coalescing-operator.433767/

関連

light11.hatenadiary.com

参考

qiita.com

issuetracker.unity3d.com

https://forum.unity.com/threads/simple-question-about-null-conditional-operator.734864/