检查Unity对象要始终使用 != null而不是?.

当使用 ?. 操作符(C# 语法糖)时,它只检查该对象在 内存(C#层面) 是否为 null,而不会检查该对象在 Unity 引擎层面是否已被销毁(Destroyed)。

为什么使用?.会报错?
对象已被销毁: 例如,当前Item 指向的是上一次点击的那个 Item。如果由于某种原因(比如列表刷新、翻页、重新打开界面),之前的那个 Item 对象已经被 Destroy 了,但变量 Item 依然保存着那一块内存地址。
?. 的局限性: 对于 Unity 的 GameObject 或 Component,obj?.SetActive() 不能检测出对象是否被销毁。当对象被销毁后, 在 Unity 看来是 "null",但在 C# 引用层面它不是 null。此时调用 SetActive 就会抛出 NullReferenceException。

核心结论: 在 Unity 中,处理 GameObject、MonoBehaviour 等继承自 UnityEngine.Object 的对象时,禁止使用 ?. 和 ?? 操作符。始终使用显式的 if (obj != null) 或 if (obj == null)。

测试代码:

cs 复制代码
public class TestUnityFakeNullDemo : MonoBehaviour
{
    private GameObject testObject;
    private Transform testTransform;

    void Start()
    {
        testObject = new GameObject("TestObject");
        testTransform = testObject.transform;

        StartCoroutine(TestSequence());
    }

    IEnumerator TestSequence()
    {
        Debug.Log("=== 第一阶段:对象存在时 ===");
        TestPhase1_ObjectExists();

        yield return new WaitForSeconds(0.5f);

        Debug.Log("\n=== 销毁对象 ===");
        Destroy(testObject);

        // 立即测试(对象标记为销毁但可能还未完全销毁)
        Debug.Log("\n=== 第二阶段:Destroy 调用后立即测试 ===");
        TestPhase2_ImmediatelyAfterDestroy();

        yield return null; // 等待一帧

        Debug.Log("\n=== 第三阶段:等待一帧后测试 ===");
        TestPhase3_AfterOneFrame();

        yield return new WaitForSeconds(1f);

        Debug.Log("\n=== 第四阶段:等待1秒后测试 ===");
        TestPhase4_AfterDelay();
    }

    void TestPhase1_ObjectExists()
    {
        Debug.Log($"testObject == null: {testObject == null}");
        Debug.Log($"testObject is null: {testObject is null}");
        Debug.Log($"testObject?.name: {testObject?.name}");
    }

    void TestPhase2_ImmediatelyAfterDestroy()
    {
        Debug.Log($"testObject == null: {testObject == null}");
        Debug.Log($"testObject is null: {testObject is null}");

        // 测试属性访问
        Debug.Log($"testObject?.name: '{testObject?.name}'");

        // 测试方法调用 - 关键!
        TestMethodCall("SetActive", () => { testObject?.SetActive(false);return 0; });
        TestMethodCall("GetComponent", () => testObject?.GetComponent<Transform>());
        TestMethodCall("transform.position", () => {
            var pos = testObject?.transform?.position;
            return pos;
        });
    }

    void TestPhase3_AfterOneFrame()
    {
        Debug.Log($"testObject == null: {testObject == null}");
        Debug.Log($"testObject is null: {testObject is null}");

        TestMethodCall("SetActive (1帧后)", () => { testObject?.SetActive(false); return 0; });

        // 测试 Component 引用
        Debug.Log($"\ntestTransform == null: {testTransform == null}");
        Debug.Log($"testTransform is null: {testTransform is null}");
        TestMethodCall("Transform.position", () => {
            var pos = testTransform?.position;
            return pos;
        });
    }

    void TestPhase4_AfterDelay()
    {
        Debug.Log($"testObject == null: {testObject == null}");

        // 尝试不使用 ?. 
        Debug.Log("\n--- 直接访问(应该抛异常)---");
        try
        {
            testObject.SetActive(false);
            Debug.Log("✓ 没有异常(不应该)");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"✗ 异常: {e.GetType().Name}");
        }

        // 正确的检查方式
        Debug.Log("\n--- 使用 != null 检查 ---");
        if (testObject != null)
        {
            Debug.Log("对象存在");
        }
        else
        {
            Debug.Log("✓ 正确识别对象已销毁");
        }
    }

    void TestMethodCall(string name, System.Func<object> action)
    {
        Debug.Log($"\n测试: {name}");
        try
        {
            var result = action();
            Debug.Log($"✓ 成功执行,结果: {result}");
        }
        catch (MissingReferenceException e)
        {
            Debug.LogError($"✗ MissingReferenceException: {e.Message}");
        }
        catch (System.NullReferenceException e)
        {
            Debug.LogError($"✗ NullReferenceException: {e.Message}");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"✗ {e.GetType().Name}: {e.Message}");
        }
    }
}

结果:

测试代码2:

cs 复制代码
using UnityEngine;

public class RealWorldExample : MonoBehaviour
{
    private GameObject m_goSelected;
    
    void Start()
    {
        // 创建一个带渲染器的对象
        m_goSelected = GameObject.CreatePrimitive(PrimitiveType.Cube);
        m_goSelected.name = "SelectedCube";
        
        Debug.Log("按空格键销毁对象,按S键测试操作");
    }
    
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Debug.Log("=== 销毁对象 ===");
            Destroy(m_goSelected);
        }
        
        if (Input.GetKeyDown(KeyCode.S))
        {
            TestOperations();
        }
    }
    
    void TestOperations()
    {
        Debug.Log("\n=== 开始测试 ===");
        Debug.Log($"m_goSelected == null: {m_goSelected == null}");
        Debug.Log($"m_goSelected is null: {m_goSelected is null}");
        
        // 方法1: 使用 ?.
        Debug.Log("\n方法1: 使用 ?.");
        try
        {
            m_goSelected?.SetActive(false);
            Debug.Log("✓ m_goSelected?.SetActive(false) 执行成功");
            
            // 再次激活
            m_goSelected?.SetActive(true);
            Debug.Log("✓ m_goSelected?.SetActive(true) 执行成功");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"✗ 异常: {e.GetType().Name}: {e.Message}");
        }
        
        // 方法2: 不使用检查直接调用
        Debug.Log("\n方法2: 直接调用");
        try
        {
            m_goSelected.SetActive(false);
            Debug.Log("✓ 直接调用成功");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"✗ 异常: {e.GetType().Name}: {e.Message}");
        }
        
        // 方法3: 使用 != null 检查
        Debug.Log("\n方法3: 使用 != null");
        if (m_goSelected != null)
        {
            m_goSelected.SetActive(false);
            Debug.Log("✓ 检查通过并执行");
        }
        else
        {
            Debug.Log("✓ 检查失败,对象已销毁");
        }
        
        // 方法4: 测试其他Unity API
        Debug.Log("\n方法4: 测试 GetComponent");
        try
        {
            var renderer = m_goSelected?.GetComponent<Renderer>();
            Debug.Log($"✓ GetComponent 结果: {renderer}");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"✗ 异常: {e.GetType().Name}");
        }
    }
}

结果:

相关推荐
郝学胜-神的一滴1 小时前
[简化版 GAMES 104] 现代游戏引擎 02:拆解现代游戏引擎5+1层级架构,吃透引擎底层核心逻辑
c++·unity·架构·游戏引擎·图形渲染·unreal engine·系统设计
LONGZETECH3 小时前
新能源汽车充电设备装配与调试仿真教学软件 技术架构与核心实现解析
大数据·算法·3d·unity·架构·汽车
丁小未4 小时前
Unity 几种常见合批手段的要求
游戏·unity·合批·srpbatcher·动态合批·静态合批
旧物有情1 天前
游戏开发常用架构 #MVP,MVC
游戏·unity·架构·mvc
勇踏前人未索之境1 天前
Unity打包运行于鸿蒙手机
unity·智能手机·harmonyos
玖玥拾1 天前
Unity 3D 笔记(十一)UI 框架进阶:栈弹窗交互、BasePanel 基类虚方法、DoTween 界面动画
笔记·3d·unity
郝学胜-神的一滴1 天前
中级OpenGL教程 023:Assimp模型加载全解——从源码到架构的骈文探秘
c++·unity·游戏引擎·cmake·unreal engine·opengl
xcLeigh2 天前
Unity基础:GameObject与Component——Unity核心架构思想彻底理解
unity·教程·component·gameobject
郝学胜-神的一滴2 天前
中级OpenGL教程 022:探秘三维世界的血脉传承——物体父子关系与矩阵递归奥义
c++·线性代数·unity·矩阵·游戏引擎·unreal engine·opengl