C#
Unity
ReSharper
Rider

Reshaper/Riderの「Possible unintended bypass of lifetime check of underlying Unity engine object」って何ぞや?

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では、こんな感じで出ます。

キャプチャ.PNG

この投稿ではその背景・内容を紹介します。

背景知識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の生存状態で==!=の結果が変わるの、分かりにくい仕様だと思うんですよね。。。