5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

横国ゲーム制作部Advent Calendar 2022

Day 16

Unityにおけるnullチェック(3) - A is nullとA == null

Last updated at Posted at 2022-12-16

以下の続きの記事になります。

簡単におさらいするのでこの記事だけ読んでも問題ないとは思いますが、お時間があれば是非(1)、(2)の記事もお読み下さい。

検証内容のおさらい

  • UnityEngine.Objectでは等値演算子==(及び非等値演算子!=)がオーバーロードされている
  • オーバーロードされた等値演算子ではObject.CompareBaseObjectsメソッドに判定処理を委譲している
public static bool operator ==(Object x, Object y) => Object.CompareBaseObjects(x, y);

public static bool operator !=(Object x, Object y) => !Object.CompareBaseObjects(x, y);
  • Object.CompareBaseObjectsメソッド内では純粋なnullチェックやInstanceIDの等値判定に加えて、渡されたオブジェクトが「破棄」されていないか(Unityフレームワーク上で生存しているか)をチェックするObject.IsNativeObjectAliveメソッドが実行されている

  • Destroy実行前後で、検証対象メソッドの戻り値は以下のように変化した(UnityEngine.Objectに生えている静的な非公開メソッド)

IsNativeObjectAlive DoesObjectWithInstanceIDExist GetCachedPtr
破棄前 true true インスタンス固有の値
破棄後 false false 0

nullチェックの記法

インスタンスがnullでないことを確認する書き方はいくつかあります。

// 1. 標準的なnullチェックの書き方
if (sampleMonoBehaviour != null)
{
}

// 2. UnityEngine.Object→boolへの暗黙的な型変換が定義されているため、こう書いても1と同じ意味
if (sampleMonoBehaviour)
{
}

// 3. C# 7.0で導入された「is」と、C# 9.0で導入された「not」を使ったnullチェック
// これは1と2とは挙動が異なる
if (sampleMonoBehaviour is not null)
{
}

1番目の書き方は最も一般的なnullチェックの書き方だと思います。
これがどのような挙動を示すかは、記事(1)と(2)で検証し、この記事のおさらいにも書きました。
2番目は記事(1)の余談でも触れましたが、暗黙的な型変換演算子がオーバーロードされているために許容される記述です。

問題は3番目の書き方です。
これは、C# 7.0で導入されたパターンマッチングという文法でis演算子の拡張(元々isは型の一致判定を行う用途などに使われていた)が行われたことによって実現できるようになった記述です。
この場合null「でない」ことをチェックしているため、is notのように書いていますが、notがこの形で使えるようになったのはC# 9.0以降でそれ以前は!(A is null)のように記述していました。
直感的には1番と3番は同じ意味に見えますが、is演算子は等値演算子==と異なりユーザがオーバーロードした演算子を考慮しません
つまり、isは「その変数に本当にnullが代入されているか」を確認します。
この挙動は使用している多くの型が等値演算子をオーバーロードするUnityEngine.Objectを継承しているUnityの場合だと大きく問題になります。
以下のようなテストコードを書いて見ます。

SampleTest.cs
using System.Collections;
using UnityEngine;

public class SampleTest : MonoBehaviour
{
    private IEnumerator Start()
    {
        SampleMonoBehaviour sampleMonoBehaviour = gameObject.AddComponent<SampleMonoBehaviour>();

        Debug.Log("Destroy前");
        Debug.Log($"{nameof(sampleMonoBehaviour)} == null: {sampleMonoBehaviour == null}");
        Debug.Log($"{nameof(sampleMonoBehaviour)} is null: {sampleMonoBehaviour is null}");

        Destroy(sampleMonoBehaviour);
        // 1フレーム待つ
        yield return null;

        Debug.Log("Destroy後");
        Debug.Log($"{nameof(sampleMonoBehaviour)} == null: {sampleMonoBehaviour == null}");
        Debug.Log($"{nameof(sampleMonoBehaviour)} is null: {sampleMonoBehaviour is null}");
    }
}

実行結果は以下のようになります。
スクリーンショット 2022-12-16 235315.png

このように、「破棄」されているかの判定を実装している==を無視することで、破棄後も破棄されたインスタンス is nullfalseを返してしまうことが確認できました。
このため、UnityEngine.Objectに対するnullチェック(および破棄判定)はisは用いないほうが良さそうです。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?