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
がスローされてしまいます。
また、次のように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がスローされます。
さらにこのような問題は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を継承したクラスではこれらの演算子を使うな」ということになるようです。。
残念な話ですが、仕方ないですね。
Unity内でもどうするべきか議論はされているようです。
https://forum.unity.com/threads/elvis-operator-null-coalescing-operator.433767/
関連
参考
https://forum.unity.com/threads/simple-question-about-null-conditional-operator.734864/