检查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}");
        }
    }
}

结果:

相关推荐
张老师带你学12 小时前
unity TerrainSampleAssets
科技·游戏·unity·游戏引擎·模型
RReality15 小时前
【Unity Shader URP】色带渐变着色(Ramp Shading)实战教程
ui·unity·游戏引擎·图形渲染
mxwin1 天前
Unity URP 体积光与雾效 基于深度重建世界空间位置,实现体积雾与体积光
unity·游戏引擎
张老师带你学1 天前
unity 树资源 有樱花树 buildin
科技·游戏·unity·游戏引擎·模型
魔士于安1 天前
unity 植物 不常见 花 触手植物
游戏·unity·游戏引擎·贴图·模型
魔士于安1 天前
unity=>传送门特效 带自由视角旋转放大 鼠标操作
前端·游戏·unity·游戏引擎·贴图·模型
南無忘码至尊1 天前
Unity学习90天 - 第4天 - 认识物理系统基础并实现物体碰撞反弹
学习·unity·游戏引擎
南無忘码至尊1 天前
Unity学习90天 - 第4天 - 学习预制体 Prefab + 实例化并实现按鼠标生成子弹
学习·unity·游戏引擎
魔士于安2 天前
Unity资源Toon City Pack 发电厂 工地 公园 地铁站口 银行 车 直升飞机 可动 URP
游戏·unity·游戏引擎·贴图·模型
心前阳光2 天前
Unity之运行时标准材质半透明无效果
unity·游戏引擎·材质