Using reflection, sometimes a field is "null" and sometimes it is "Null"?

I’ve been writing a generic serialization system over the past few days and it’s getting close to complete right now. I put in a null check to make sure no empty fields are passed in (and thus don’t need to be serialized) and I noticed a result that I don’t understand.

I have a sample script named TestScript which looks like this:

public class TestScript : MonoBehaviour 
{
    public Texture2D texture;
    public Material mat;
    public GameObject gobj;
    public OtherScript other;
}

When I send this to the serializer, I get all the fields off of TestScript using FieldInfo. I then check to see if these field info’s are null. This is the comparer:

        //field is a FieldInfo. We get the value from an instance that it exists on. In This case target.
       object obj = field.GetValue(target);//Get the value of this field on our target script
       Debug.Log(obj);
       if(obj.Equals(null))
       {
            return;
       }

When comparing the Texture2D, the Material, and the GameObject, I get a return type of “null”. When I try to compare the class OtherScript, I get a return type of “Null”. The “Null” return throws a NullReferenceException at this line and the others don’t. Why is this and how can I check to make sure the field OtherScript is not null?

I’m pretty sure you just stumbled on that little “null trick” Unity implements for all objects derived from UnityEngine.Object.

As you might know objects derived from UnityEngine.Object usually have a native counterpart on the C++ side of the engine. Since C# uses a managed memory space and C++ not there are some problems with this object “duality”. In C++ (and Unity) you can actually “destroy” / free an object manually. In C# that’s not possible since the managed memory is released when all references to an object go out of extent.

Now we have the method Destroy which can destroy all objects derived from UnityEngine.Object. So how does this work in the managed environment? The answer is: it actually doesn’T work. It’s not possible to destroy a managed object. When you call Destroy on a Component Unity just destroys the native part on the C++ side. The managed object is still there until all references to that object are gone.

Unity uses a little “trick” to make a reference to such a “dead object” appear null by simply overloading the Equals method and == operator. So when you compare the reference to null they will return true, even the reference isn’t null. Though since the object is actually dead you can’t use it anymore.

The same thing happens to MonoBehaviours objects that never had a native part. This happens when you create an instance of a MonoBehaviour with the “new” keyword. This will create a managed object, but since the component isn’t attached to a GameObject it’s in it’s dead state from the very beginning.

So first rule No1: Never use the new keyword on MonoBehaviour derived classes.

btw:
This comparison makes no sense at all:

if(obj.Equals(null))

If obj is actually null this will throw a NullReferenceException since you can’t call Equals on a null object. It’s possible to call it on a “fake-null” object since you actually have an object.

Since the == operator is not a virtual method you will notice a difference when using a variable of a type derived from UnityEngine.Object and a variable of type System.Object:

SomeMonoBehaviour A = new SomeMonoBehaviour();  // create fake null object

Debug.Log("A == null:  " + (A == null));
Debug.Log("A.Equals(null) :  " + (A.Equals(null)));

object B = A;  // assign the same reference to an System.Object variable

Debug.Log("B == null:  " + (B == null));
Debug.Log("B.Equals(null) :  " + (B.Equals(null)));

The result will be:

"A == null:  true"
"A.Equals(null) :  true"
"B == null:  false"
"B.Equals(null) :  true"

So knowing that little fact you can test a “seamingly” null reference for being fake null by casting it into “object” do a == check for null and an Equals check afterwards like this:

bool IsReferenceFakeNull(object aRef)
{
    return aRef != null && aRef.Equals(null);
}

This method is safe for any reference as true null values won’t trigger the Equals check which would cause a null-ref-exception otherwise.

What does “GetValue” do?

Your null check is a little weird, if obj is actually “null” then you can’t all a method (“Equals”) on the object. Also, if the object is null you are trying to call the ToString method on it that, again, can’t be called since the object is null at that point.

To check if a variable is null you should use:

if (obj == null) {
    Debug.Log("obj is null"); //you can't call ToString on a null pointer
    return;
}

For the class OtherScript your GetValue function is returning a null pointer, that’s why your code crashes. For the rest, did you ever had one of the values actually printing that Debug.Log message? If you had, then the method it’s returning… something else, I’m not sure what could it be, but it’s not a “null”, is something else that has a ToString method that returns “null” when called, and a redefinition of Equals that returns true when null is passed.