JetBrains製で「赤ペン先生」とも呼ばれるVisual Studioプラグイン「Reshaper」、そしてそれを搭載した.NET向けIDE「Rider」
素晴らしい機能を多く持っている、Reshaper/Rider。愛用しているUnity開発者も、多いのではないでしょうか?
そんなReshaper/Riderの機能の一つ「Code Inspections」。コードの良くない部分を指摘してくれたり、「こうすればよくなるよ」という修正をしてくれます。
さて、Unity固有の「Code Inspections」には、ちょっと背景知識が必要でわかりにくいものがあります。それが、
「Possible unintended bypass of lifetime check of underlying Unity engine object」
です。
Riderでは、こんな感じで出ます。
この投稿ではその背景・内容を紹介します。
背景知識1
GameObjectやComponentをnullと比較するとき、落し穴があります。
[SerializeField] private GameObject target; // 参照している。nullじゃない。
private void Awake()
{
Debug.Log(target == null); // Falseと表示される
DestroyImmediate(target);
Debug.Log(target == null); // Trueと表示される
}
上のコードで、変数targetがオブジェクトを参照していてnullじゃないのに、target == null
がtrueになります。
GameObjectやComponentの親クラスであるUnityEngine.Objectには、==や!=、boolのオペレーターが定義されています。
そしてその挙動が「対象のGameObjectやComponentが、生存していないならば、==でnullと比較するとtrueを返す」という仕様になっています。
関連記事 : 「GameObjectやComponentをnullと比較するときの落し穴」
背景知識2
C#で「person?.Name」と「person == null ? null : person.Name」は等価じゃありません。
null条件演算子 (?.) において、nullとの比較に 「==」オペレーターでの比較は行われなません。(型に対して==で独自定義した処理は呼ばれない)
同様に、null合体演算子 (??)でも同じように、nullとの比較に 「==」オペレーターでの比較は行われない。(型に対して==で独自定義した処理は呼ばれない)
関連記事 : 「C#で「person?.Name」と「person == null ? null : person.Name」は等価じゃない。」
背景知識1と背景知識2を合わせての注意点
次のコード1と
// コード1
[SerializeField] private GameObject target;
private void Awake()
{
DestroyImmediate(target);
Debug.Log(target == null ? null : target.name); // `Null`と表示される
}
次のコード2は、
// コード2
[SerializeField] private GameObject target;
private void Awake()
{
DestroyImmediate(target);
Debug.Log(target?.name); // `UnassignedReferenceException`が投げられる
}
は結果が異なります。
コード1では==オペレーターを使っているので、target
が生存していなため(DestroyImmediateで破壊している)、target == null
はtrueとなり、ログにNull
と表示されます。
コード2ではnull条件演算子(?.
)を使っているので、==
が呼ばれません。コード2ではオブジェクトの生存に関係なく処理が進みます。その結果、処理が進みオブジェクトが破壊されているためUnassignedReferenceException
が投げられます。
ReShaper/RiderのCode Inspectionの意味は?
「背景知識1と背景知識2を合わせての注意点」
でReShaper/RiderのCode Inspectionの
「Possible unintended bypass of lifetime check of underlying Unity engine object」
意味と理由とメリットが伝わったでしょうか?
GameObjectやComponentをnullとの比較で==
を使っている場合、ライフサイクルを加味した処理を行われます。一方で、null条件演算子(?.
)やnull合体演算子(??
)ではライフサイクルが加味されません。一見、同じように見えるコードでも違うことに十分に注意しましょう。
詳しくはこちらも!
JetBrains/resharper-unityのwiki「Possible unintended bypass of lifetime check of underlying Unity engine object」
個人的な意見
個人的には、UnityEngine.Object
の生存状態で==
や!=
の結果が変わるの、分かりにくい仕様だと思うんですよね。。。