Ever checked an object for null and it didn’t work? Computers do precisely what you tell them to do -> it must be a programming error that bypasses the null check.
This is one of the moments where I doubt my sanity as a programmer.
What Happened?
The C# target object isn’t null but the C++ object it pointed to is. In my case, the target is a MonoBehaviour referenced as an interface. The != operator, therefore, executed a pure C# null check instead of Unity’s custom null check which leads to the null reference exception.
Why?
Unity is a bit special when it comes to null checks. Most of the Unity engine is built in C++. The properties of GameObjects and UnityEngine.Objects live in the C++ part of the engine. The C# object only contains a pointer to the native C++ object.
The problem with this separation is, that the lifetime of the C++ and C# objects can be different because of memory management. The C++ object can already be destroyed while the C# object is still waiting for the garbage collector. The C# object isn’t null and passes the C# null check. But accessing it will lead to a null reference exception in the C++ engine.
To handle this problem Unity overloaded the == operator to return null for the C# object when the C++ native object is null.
This has a few problems:
- It’s slow
- Not thread-safe
- Inconsistent behaviour with the ?? operator which does a pure C# null check. (Should never be used with UnityEngine.Objects because of the above reason.)
- It does not work when the object is boxed or referenced as an interface
Solution
The problem is that the compiler executed a C# null check instead of a Unity null check. This behaviour can be fixed in two ways:
- Don’t reference a UnityEngine.Object as interface or box it
- Cast it to UnityEnigne.Object before doing the null check.
if ((target as UnityEngine.Object) != null) target.OnPointerExit();