Destroy GameObject and C#: how does it work?

Hi all,

I was wondering if one of the developers on the Unity framework or someone really familiar with how it works could answer a question that is nagging me.

I recently had the following situation. I had an object A which had a reference to object B inside its variable declarations. e.g.

public class A : MonoBehaviour
{
    B b;

//...

I had an instance of A (let’s call it “a”) that referenced an instance of B through the “b” variable.

Because of some other condition in the game the object that “b” referenced was destroyed using the Destroy method provided by Unity.

I was very surprised (but pleasantly so) to find that the “b” reference in the “a” object had been set to “null”.

My question is, how exactly does this work?

I didn’t even know you could destroy objects in C#. I thought that C# objects were garbage collected when there were no further references to the object.

So it’s seems that Unity is doing something clever with object references. How exactly is the object reference in “a” getting updated to “null”? I didn’t think this was something that could happen in C#.

Sean

It’s an old trick known as smart pointers. Unity keeps a secret “real” reference to every gameObject. Your gameObject variables don’t point to actual gameObjects – they point to the secret reference (so every look-up is really a double look-up). When you destroy a gameObject, it sets a “destroyed” flag in Unity’s secret reference. At the end of the frame, Unity unhooks the real gameObject from it’s list, letting it be garbage collected.

A---O--->the real gameObject
    /
B---

Meanwhile, everyone else with a link to that gameObject (really a link to Unity’s secret reference), gets A==null because of the secret destroyed flag (gameObject == is overloaded to check the flag).

Instead of that, they thought about directly giving every gameObject a destroyed flag. That would have been fine, and faster, except you’d always have to check if(A==null || A.destroyed==true).

3 Likes

Good to know!

I believe these are called Handles (as opposed to Pointers), and provide benefits beyond having to double test if a object reference is null. Double indirection allows systems to manage objects in memory independent of program code - a result is the benefit you noted of removing an object from the scene, but have it remain in memory and in Unity’s engine loop until the appropriate time when it can be removed. I image there are many other instances where Unity uses Handles.

To be very specific, the exact thing going on is operator overloading. In Object.cs:

        public static bool operator==(Object x, Object y) { return CompareBaseObjects(x, y); }

        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;

            // VERY LONG COMMENT, removed for brevity, you can check the source link
            if (o is MonoBehaviour || o is ScriptableObject)
                return false;

            return DoesObjectWithInstanceIDExist(o.GetInstanceID());
        }

        System.IntPtr GetCachedPtr()
        {
            return m_CachedPtr; // This is the value Owen-Reynolds was talking about that gets set to IntPtr.Zero when you destroy the object.
        }

You can also check if an object has been destroyed by:

UnityEngine.Object obj;

var isDestroyed = ((System.Object) obj) != null) && obj == null;

It’s going to depend on someone’s background and whether they like language-specific terms (in Java/C# you could say “no – it’s a smart REFERENCE”. A nice explanation, that matches the way I learned it, is in this StackExchange Q: https://stackoverflow.com/questions/13023405/what-is-the-difference-between-handle-pointer-and-reference

IMHO, if it looks and acts like a normal pointer, but does extra stuff mostly invisibly, “smart pointer” feels good. If you have to think about how to use it, “handle” feels better. Coroutine c1=…; seems like a handle, since the things you can do with are aren’t obvious, and are non-member functions.