There seems to be a lot of confusion about how Unity handles the end of the life of an object. This article is meant to shed some light on the Object.Destroy
method – what really happens and what that means to you.
What Destroy does
Let’s get this out of the way right at the beginning: Object.Destroy
does not destroy objects; it just signs objects up for destruction at the end of the frame. This means that you can still work with the object after passing a reference to it into the Destroy
method. At the end of the frame, Unity goes through its internal list of things to be destroyed, and actually destroys them.
How are objects destroyed
Short answer: They aren’t destroyed, except they actually are.
In fact, let’s go for the longer answer.
To start off, know that Unity is not a C# engine. The engine’s core, the UnityEngine.dll
that is part of every build and that you import whenever you write using UnityEngine;
, is written in C/C++. There’s good reasons for that, some of which actually have to do with how objects are destroyed.
The way Unity works to combine its C++ world with the C# world you create when you program your game is that all UnityEngine.Object
s have a representation in both worlds. As a reminder: UnityEngine.Object
is the base class for GameObject
, as well as all Component
and ScriptableObject
classes. This means that, among the other things, all MonoBehaviour
instances in your scene exist as a C# object and a C/C++ object somewhere else in your memory.
The C# object knows where its corresponding C/C++ object is located, and every aspect of the object that is defined by Unity (like the values of the properties enabled
and gameObject
) is stored in the C/C++ object.
Whenever you write someBehaviour.enabled = false
, Unity goes from someBehaviour
‘s C# representation to its corresponding C/C++ object and changes the enabled
state there.
And whenever an object is destroyed at the end of a frame due to you writing Destroy(someObject)
, Unity takes the memory space allocated for the C/C++ object and frees it, which means that it’s officially not used anymore, and new objects are allowed to store themselves in that space from now on. Effectively, though the data of the object is not immediately wiped from your memory, the object is now destroyed as there’s nothing keeping the data from being overwritten.
You might notice that there was no mention of the C# object. This is because the C# part of a UnityEngine.Object
is not destroyed by Object.Destroy
. The reason for that is that it’s simply impossible to free memory (and thus destroying objects) in safe, managed code – the type of code you write in C#.
In managed code, objects are destroyed automatically by the garbage collector when it noticed that an object cannot be used anymore because there are no references left available for accessing it. As an example:
private void Start() { var car = new Car(); }
Note how there is no reference to the newly created Car
object being stored anywhere permanent. After this method finishes running, you have no way at all to find it again in your memory; you have no way of accessing the Car
object ever again. And because of that, the garbage collector will free the memory space it takes up, effectively destroying it.
Unless you are using unsafe
code in C#, there is no way of going around this and manually destroy an object. This means that the C# side of the object will never be destroyed, unless it happens to be destroyed by the garbage collector once the conditions are met.
Now, when you try to access a method or property of your destroyed object, what happens depends on the object member you’re trying to access. If the member is part of the Unity engine, like Behaviour.enabled
or anything from a builtin component, the C# object will notice that its C/C++ counterpart is missing and throws an exception, as the part of the object that is being accessed doesn’t exist anymore. However, if the member you are accessing is defined in C#, it still exists and everything works without a problem.
You can test this for yourself with a script like this:
[SerializeField] private SomeMonoBehaviour someMonoBehaviour; private void Update() { if (Input.GetKeyDown(KeyCode.D)) { Destroy(someMonoBehaviour); } if (Input.GetKeyDown(KeyCode.F)) { // This will throw an exception if the object is destroyed Debug.Log(someMonoBehaviour.enabled); } if (Input.GetKeyDown(KeyCode.G)) { // This will work even if the object is destroyed Debug.Log(someMonoBehaviour.someField); } }
In conclusion: Object.Destroy
yields only the destruction of the C/C++ representation of an object, meaning that everything that you implemented in C# will persist, while all UnityEngine-defined properties and methods of the object become unavailable. Of course, destroying a component does two more things:
- It removes the component from its GameObject and unsubscribes it from engine events like Update; the component will not receive them anymore.
- When you compare a destroyed object with
null
using==
, the result will betrue
(orfalse
for!=
) despite the object still existing.
For your code, this means that you should always check if your object was destroyed by comparing it to null
, if you’re not doing that already. This way, you make sure not to use an object that is already destroyed, even though it’s possible.
Interfaces and UnityEngine.Objects
This section is a bit more advanced, so if you’re not planning on implementing interfaces in UnityEngine.Object
classes, it’s okay to skip it.
At first sight, it doesn’t seem much of a bad idea to make a component class implementing an interface. After all, you can even use GetComponent<ISomeInterfaceType>()
to find components implementing a specific interface.
However, looking at how Unity’s object destruction works, there are some mean traps that come with this. I’d recommend using multiple components instead of interfaces anyway; here’s some reasons for that.
First off, an interface can be implemented by classes that don’t inherit from UnityEngine.Object
. This means that when you have a reference to an object, and the type of the variable is an interface, C# cannot be sure that the object you are referencing is a UnityEngine.Object
. If it is not, you wouldn’t be able to do specific things with it, like destroying it.
private void Start() { var someObject = GetComponent<ISomeInterfaceType>(); // This won't work because C# doesn't know whether this is a UnityEngine.Object // Destroy(someObject); // Instead, you'd have to do this Destroy((Object)someObject); }
At least, this issue is something the compiler will detect and tell you. However, the compiler will not keep you from checking whether an object was destroyed by comparing it to null
. This check will not work, as it’s implemented in UnityEngine.Object
and thus will not be used if the referenced object is not handled as one:
private ISomeInterfaceType someObject; private void Start() { someObject = GetComponent<ISomeInterfaceType>(); Destroy((Object)someObject); } private void Update() { // This will print "False", even though the object is destroyed Debug.Log(someObject != null); // This will print "True" because we're using Unity's null comparison Debug.Log((Object)someObject != null); }
Because the compiler will not tell you that you’re not using Unity’s “is the object destroyed?” null check, you might run into a situation where you use a destroyed object despite checking whether it is destroyed – just because you referenced it through its interface type. I recommend avoiding interfaces on UnityEngine.Object
s altogether, but if you insist on using them, be aware of this trap.
In conclusion
Unity’s object destruction is pretty complicated and confusing. Due to limitation of the (managed) C# language and the dual nature of UnityEngine.Object
s, only parts of your components and ScriptableObjects are destroyed when calling Object.Destroy
.
While there will be an exception thrown if you try to access the destroyed part of the object (which is everything that comes from the UnityEngine world rather than your C# code), you will have to be careful to not continue using the C# part of a destroyed object as that will still exist.