我注意到,如果我有一些变量暴露给 Unity 检查器,例如:
[SerializeField] GameObject _tickIcon;
如果我将它们保留为未分配并尝试使用 null 条件运算符并在该对象上调用方法,则会收到一个异常,指出未分配变量。所以基本上不是这样做:
_tickIcon?.SetActive(false);
它迫使我这样做:
if(_tickIcon != null)
{
_tickIcon.SetActive(false)
}
所以我猜这一定是特定于 Unity 运行时的东西,它并不是真正的空,但我可以检查空并且它可以工作。我真的不明白这一点。
它通常不适用于从UnityEngine.Object
继承的任何内容!
tl;dr:??
和?.
运算符在System.Object
(acaobject
(级别工作,而 Unity 的==
运算符在UnityEngine.Object
级别工作。
由于他们在内部以不同的方式实现==
/!=
方法,因此被绕过了。请参阅自定义 == 运算符,我们应该保留它吗?
ReSharper在可能无意中绕过底层Unity引擎对象的生命周期检查中很好地解释了它
如果派生自
UnityEngine.Object
的类型使用空合并 (??
( 或空传播或条件 (?.
( 运算符,则会显示此警告。这些运算符不使用在UnityEngine.Object
上声明的自定义相等运算符,因此会绕过检查以查看底层本机 Unity 引擎对象是否已销毁。为了澄清意图,最好使用显式null
或布尔比较,或调用System.Object.ReferenceEquals()
。
UnityEngine.Object
在某些情况下并不是真正null
,但仍保留一些元数据。所以底层object
(=System.Object
(不是null
,UnityEngine.Object
的覆盖==
运算符只是返回== null
的true
。
原因是:c#
层UnityEngine.Object
只是实际底层c++
引擎代码之上的开发人员 API 层。自定义==
和implicit bool operator
基本上都归结为
(源代码(
static bool CompareBaseObjects(UnityEngine.Object lhs, UnityEngine.Object rhs) { bool lhsNull = ((object)lhs) == null; bool rhsNull = ((object)rhs) == null; if (rhsNull && lhsNull) return true; if (rhsNull) return !IsNativeObjectAlive(lhs); if (lhsNull) return !IsNativeObjectAlive(rhs); return lhs.m_InstanceID == rhs.m_InstanceID; } ... static bool IsNativeObjectAlive(UnityEngine.Object o) { if (o.GetCachedPtr() != IntPtr.Zero) return true; //Ressurection of assets is complicated. //For almost all cases, if you have a c# wrapper for an asset like a material, //if the material gets moved, or deleted, and later placed back, the persistentmanager //will ensure it will come back with the same instanceid. //in this case, we want the old c# wrapper to still "work". //we only support this behaviour in the editor, even though there //are some cases in the player where this could happen too. (when unloading things from assetbundles) //supporting this makes all operator== slow though, so we decided to not support it in the player. // //we have an exception for assets that "are" a c# object, like a MonoBehaviour in a prefab, and a ScriptableObject. //in this case, the asset "is" the c# object, and you cannot actually pretend //the old wrapper points to the new c# object. this is why we make an exception in the operator== //for this case. If we had a c# wrapper to a persistent monobehaviour, and that one gets //destroyed, and placed back with the same instanceID, we still will say that the old //c# object is null. if (o is MonoBehaviour || o is ScriptableObject) return false; return DoesObjectWithInstanceIDExist(o.GetInstanceID()); }
后台调用本机引擎代码,在这些代码中处理这些对象的实际实例并控制其生命周期。因此,例如,在Destroy
对象后,在本机c++
引擎中,该对象已被标记为已销毁,并且自定义==
和bool operator
已经返回false
。c#
层UnityEngine.Object
仍然存在,直到它被实际垃圾回收(通常在帧的末尾,但也没有真正的保证(。
因此,像_tickIcon?.gameObjct
抛出NullReferenceException
这样的事情的主要原因是?.
运算符只直接在底层object
(System.Object
(上工作,而UnityEngine.Object
则在不同的级别上处理他们的自定义实现。
例如之后
Destroy(_tickIcon);
_tickIcon.SetActive(false);
您会注意到,您不会得到一个正常的NullReferenceException
如果它实际上是null
的情况,而是得到一个 Unity 海关MissingReferenceException
告诉您为什么抛出异常的可能原因。
长话短说:作为解决方案UnityEngine.Object
具有隐式bool
运算符
对象是否存在?
您应该始终检查从UnityEngine.Object
派生的任何内容是否存在,如下所示:
if(_tickIcon)
{
_tickIcon.SetActive(false);
}
或显式(因为这将使用自定义==
/!=
运算符(
if(_tickIcon != null)
{
_tickIcon.SetActive(false);
}
只是为了重新混合这个问题的语法,当尝试这样的事情时:
//Error
Vector3 targetPos = target?.position ?? Vector3.zero;
相反,请尝试以下操作:
//OK
Vector3 targetPos = target ? target.position : Vector3.zero;
在我的情况下,销毁的对象仅在执行空检查时返回表达式my_obj.Equals(null)
的true
。所有其他(例如my_obj == null
或my_obj is null
(返回false
。