🧩 Unity 对象判空机制混乱原因总结(上篇)
一、问题的起源:为什么 Unity 的判空让人迷惑?
在普通 C# 里:
csharp
if (obj == null) // 或者 obj is null
这两种写法都只判断 C# 层的"引用是否为空"。
但是在 Unity 里,这就不一定对了。因为 Unity 的对象底层是 C++ 实现的,C# 层只是它的一个"壳子"。
✅ 简单说:你看到的 GameObject / Component 等等,表面上是 C# 对象,其实内部绑定着一个 C++ 实体。
二、为什么 Unity 的判空有"假 null"
Unity 重载了 == 运算符。
也就是说:
csharp
if (go == null)
这个判断 并不是在判断 C# 引用是否为空,而是调用了 Unity 的特殊逻辑:
👉 它会去底层(C++)查一下,这个对象对应的 native 实体是不是已经销毁(Destroyed)。
这就导致一种奇怪的现象:
csharp
var go = SomeGameObject;
Destroy(go);
if (go == null) // true
但其实此时 go 这个变量在 C# 层不是 null ,只是它对应的 C++ 对象没了。
这就被称为 "假 null" (fake null)。
三、为什么有人说"得看 C++ 层"
因为 Unity 的对象是否"存在",最终取决于 C++ 的 native 实体 。
C# 层的引用只是一个代理,所以:
go is null只判断 C# 引用是否为空go == null会额外判断 底层 native 对象是否还在
四、不同开发者的看法分歧
在讨论中,大致有两派观点:
🔹 1. 实用派:
认为:
"有些 Unity API 的确会遇到这种问题(native 对象被销毁但引用还在),比如 NativeArray 操作 Mesh 的情况。
所以需要检测是否还存在,否则会出 bug。"
他们接受这种检查逻辑,认为这是 Unity 引擎的特性。
🔹 2. 设计派:
认为:
"如果需要关心底层对象是不是销毁,那说明逻辑设计有问题。
应该从上层逻辑上绕开这种情况,不应该让业务层直接面对 C++ 层的销毁状态。"
比如通过封装或生命周期管理,避免出现"悬空引用"。
五、常见的坑
| 情况 | 结果 |
|---|---|
obj == null |
调用了 Unity 的重载逻辑,可能为 true(假 null) |
obj is null |
只判断 C# 层是否为空(不会检查 C++) |
Destroy(obj) 后访问 |
变量还在,但底层对象没了;访问属性或方法会异常 |
| API 用 NativeArray 或底层接口 | 可能需要检测"是否已销毁",否则崩溃 |
✅ 小结
- Unity 对象其实是 C# 包着 C++ 的壳。
== null判断的是 "对象是否销毁",不只是空引用。- "假 null" 就是:引用非空,但底层对象没了。
- 对于普通业务逻辑,应尽量避免直接依赖底层状态(通过封装或生命周期管理解决)。