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

结果:

相关推荐
云上空13 小时前
腾讯云使用对象存储托管并分享WebGL小游戏(unity3d)(需要域名)
unity·腾讯云·webgl·游戏开发·对象存储·网页托管
小贺儿开发15 小时前
Unity3D VR党史主题展馆
unity·人机交互·vr·urp·展馆·党史
TopGames15 小时前
Unity实现10万人同屏动态避障和导航寻路系统 支持3D地形
unity·性能优化·游戏引擎
在路上看风景18 小时前
01. GUIContent
unity
托洛夫斯基扎沙耶19 小时前
Unity中状态机与行为树的简单实现
unity·游戏引擎
TrudgeCarrot1 天前
unity打包使用SPB管线出现 DontSava错误解决
unity·游戏引擎·dontsave
3D霸霸1 天前
unity 创建URP新场景
unity·游戏引擎
玉梅小洋2 天前
Unity 2D游戏开发 Ruby‘s Adventure 1:课程介绍和资源导入
游戏·unity·游戏引擎·游戏程序·ruby
托洛夫斯基扎沙耶2 天前
Unity可视化工具链基础
unity·编辑器·游戏引擎