- Home /
Testing for null, bug or feature?
Hi there,
I've noticed that if I have an object which inherits from UnityEngine.Object, and it gets destroyed and so becomes null, the following happens:
Debug.Log( myObject ); // Outputs "null"
if( myObject == null )
{
// This code doesn't run?
}
if( !myObject )
{
// But this code does
}
So testing for null explicitly doesn't work, presumably this is because there is actually an object there, but it has to behave as if it is null.
Now, I don't like using the if( !myObject ) test, I think it's bad practice. Even so, for the particular code I'm writing now I actually can't use that, as I'm working with System.Object instances which may or may not also be UnityEngine.Object instances.
So, is this a bug, or a feature?
Edit: This code will very quickly show the problem I'm talking about:
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
private System.Object go;
private System.Object t;
public void Start()
{
GameObject temp = new GameObject( "go" );
this.go = temp;
this.t = temp.transform;
Destroy( temp );
}
public void Update()
{
Debug.Log( this.go == null );
Debug.Log( this.t == null );
}
}
yes it's exactly like your code says
if( myObject == null )
{
// This code doesn't run?
}
if( !myObject )
{
// But this code does
}
if (myObject == false){
// this code will run even if object is null
}
and yes it's a feature you aren't checking if the object exists but you are trying to use the object
so giving ! before object is simply asking if it's not true and not asking if it's not null
That doesn't make sense, ! before the object is short hand for a null check.
The implicit null check only works if the object overloads the !-operator and return a boolean value otherwise it will give a compiler error. It's not a default behavior in c# so you usually check with == , != or Equals().
Looking at ILSpy => UnityEngine.Object implements only != , == , Equals and implicit operator bool, which is what gets called for if(obj) or if(!obj). All of those do more than just a simple null check.
Answer by TonyLi · Aug 27, 2013 at 05:23 PM
UnityEngine.Object overrides Equals(). The null-coalescing operator (??) also doesn't work. It's just the way Unity works. This forum thread has a deeper discussion on the issue.
But if UnityEngine.Object was properly overridden then it wouldn't be a problem surely?
Like @Jamora wrote in @Bunny83's thread, and just as in the forum thread I linked you to, UnityEngine.Object implements the null object pattern so Destroy can work cleanly. You break the pattern by casting back to System.Object. If you have a UnityEngine.Object, you should always keep a reference as a UnityEngine.Object or descendant.
System.Object.Equals is virtual, so it shouldn't matter that it's casted back to a System.Object
It matters if Unity never actually sets it to null. I think @Bunny83 did a good job of explaining in a comment below his answer.
I understand what's been said, but still I'm pretty sure that this shouldn't happen if Equals is properly overridden as it is virtual.
Answer by Bunny83 · Aug 27, 2013 at 02:37 PM
I can't reproduce this behaviour. Is it possible that you use Destroy and test the object reference in the same frame? The object is destroyed the next frame....
If you wait at least one frame or use DestroyImmediate it works as expected
var GO = new GameObject("Test");
Debug.Log("GO == null: " + (GO == null));
Debug.Log("GO != null: " + (GO != null));
Debug.Log("!GO: " + (!GO));
Debug.Log("GO == false: " + (GO == false));
Debug.Log("GO != false: " + (GO != false));
GameObject.Destroy(GO);
yield return null;
Debug.Log("Destroy");
Debug.Log("GO == null: " + (GO == null));
Debug.Log("GO != null: " + (GO != null));
Debug.Log("!GO: " + (!GO));
Debug.Log("GO == false: " + (GO == false));
Debug.Log("GO != false: " + (GO != false));
This gives me:
GO == null: False
GO != null: True
!GO: False
GO == false: False
GO != false: True
Destroy
GO == null: True
GO != null: False
!GO: True
GO == false: True
GO != false: False
I'm checking every frame, here's the code I used:
using UnityEngine;
public class NewBehaviourScript : $$anonymous$$onoBehaviour
{
private GameObject go;
private Transform t;
public void Start()
{
this.go = new GameObject( "go" );
this.t = this.go.transform;
Destroy( this.go );
}
public void Update()
{
Debug.Log( this.go == null );
Debug.Log( (bool)this.go );
Debug.Log( this.t == null );
Debug.Log( (bool)this.t );
}
}
Even with your test i get the desired output:
First frame:
false
true
false
true
because the object is still there
from second frame on:
true
false
true
false
Oh yeah, you're right, I messed that up. This code on the other hand, does show the problem - it seems the reference has to have System.Object type:
using UnityEngine;
public class NewBehaviourScript : $$anonymous$$onoBehaviour
{
private System.Object go;
private System.Object t;
public void Start()
{
GameObject temp = new GameObject( "go" );
this.go = temp;
this.t = temp.transform;
Destroy( temp );
}
public void Update()
{
Debug.Log( this.go == null );
Debug.Log( this.t == null );
}
}
Well in Unity you usually don't work with System.Object references. System.Object uses it's own Equals function which of course "sees" the actual instance which is still there.
You can use this to check a reference if it's null or "fake null":
public static bool IsNull(System.Object aObj)
{
return aObj == null || aObj.Equals(null);
}
You can also make it an extension method if you like ;) This does work with "normal" classes as well as with UnityEngine.Object classes.
Yeah I figured out that workaround, but if UnityEngine.Object was properly overridden then it wouldn't be a problem surely?