'is null' returns false on null objects

I was converting all my obj == null comparisons to the C# 7 optimized form obj is null. This is quite faster than == null, as == is an operator that may be overriden and causes some overhead that can be seen in the profiler.

However, I've verified that is null returns true on objects that are actually null. Just add this script to a new, empty GameObject:

using UnityEngine;

public class NullTest : MonoBehaviour
{
    void Start ()
    {
        MeshFilter nonExisting = GetComponent<MeshFilter>();

        Debug.Log(nonExisting);             // null
        Debug.Log(nonExisting == null);     // true
        Debug.Log(nonExisting is null);     // false  (!!!)
        Debug.Log(nonExisting is object);   // true   (!!!)

        MeshFilter nullObject = null;
        Debug.Log(nullObject);              // Null
        Debug.Log(nullObject == null);      // true
        Debug.Log(nullObject is null);      // true
        Debug.Log(nullObject is object);    // false
    }
}

How is this possible? This renders the is null form totally useless.

The capitalization when debugging the object itself is slightly different (null vs. Null). So it seems that GetComponent is returning a non-null object, but with the == operator overloaded so it returns true when compared with null. Is this really the intended behavior?

object.ReferenceEquals provide the same result as the is keyword.

1 Like

Unfortunately, unity's objects depend of that custom null comparison. So, just like you can't trust the ?. operator, you can't trust is null.

2 Likes

It's interesting that almost on a daily basis there's someone somewhere in the world discovering the custom == operator of Unity ^^. I think we had a gazillion threads and UA questions about that already.

1 Like


Silly me, how should I ever expected <null object> is null to return true?

Not surprisingly, when something works the opposite of what it is obviously supposed to do, then everyone expects it to work the correct way until they find out that it doesn't.

1 Like


It seems that the ? operator can actually be trusted. Casting the object to boolean returns False on null and True on non-null, in both Unity and standard objects.

EDIT: Unless you refer to the null-coalescing operator (??), which performs the same comparison as the "is" keyword, so it doesn't work with Unity objects.

3 Likes

There was never any chance is null could have worked, no matter what Unity did. The Destroy command takes 1 reference to a gameObject and magically makes every other reference turn to null? That's impossible. If you have g=cat1, no one can change g1 to null if all they have is another reference to cat. The best they can do to is zero-out every cat variable. That's basic reference-type stuff. Unity had to use a trick. But they didn't break how null-checks work -- the concept of the very useful Destroy command did that.

Unless you reference it via System.Object or an interface type. It'll then use System.Object's static equality operator and voila, there you go again.

OK, so...I understand why calling Destroy on an object can't turn all references to that object into true nulls.

But why does GetComponent (in the OP's test code) return a pretend-null object instead of a true null?

Debugging purposes in the editor.
(See [1] @ https://blogs.unity3d.com/2014/05/16/custom-operator-should-we-keep-it/)

2 Likes


Watch out, he didn't mean the ternary operator ?: but the null conditional operators like ?. or ?[ ].

Also you can not cast objects to boolean. This only works when the type implements an implicit or explicit type conversion operator which UnityEngine.Object did.

While I do agree that you can easily fall for this detail, on the other hand it's an intrinsic detail of how Unity uses the managed layer as scripting language and how Unity actually works under the hood. Unity is not a C# engine and at the interface between the engine core and the managed scripting layer there are several kinda unusual phenomenons.

You already have the wrong initial assumption, If "is null" returns false the object is not null. This statement is always true. However "not null" UnityEngine.Objects can be "unusable" and they fake to be null to indicate that state. As it was explained in the blog I linked they consider this a bad design decision themselfs, but it's with Unity for over 10 years now. They have thought about changing it, however it would be a huge breaking change in almost every single project out there and would also affect a lot of assets in the store. I'm sure that are the main reasons why they haven't done this step yet.

I'm sorry if it sounded condescending. I didn't mean to imply that this behaviour is obvious. As I said there are probably several dozens cases which do not behave as you might expect. Though understanding the relationship between the engine and the scripting layer usually helps to not fall for those issues and to better understand why it behaves as it does. Another huge part of Unity that doesn't behave as most would expect is serialization. So reading the script serialization docs is highly recommended if you're new to Unity or if you have never heard about it ^^.

1 Like


THIS is the actual issue. Maybe I'm missing something evident, but just returning a true null instead of a fake null in GetComponent would make the is keyword and all other null-related operators work as the C# language expects. At the same time the standard comparison operators (== null, != null) will keep working as always.


I can't see how returning a true null in GetComponent could be a huge dealbreaker, again unless I'm missing something evident. Surely Unity didn't have this problem back in 2014, but now we have C# 6, 7, etc with new null-checking operators that don't work as evidently expected with the values returned by GetComponent.

I've extended the test code, see below. As you can see, the unexpected problems arise only with the fake-null value returned by GetComponent and the new C# 6+ operators (marked with !!! below). All other cases work just fine. Anyone feel free to propose new test cases.

using UnityEngine;

public class NullTest : MonoBehaviour
{
    class TestClass
    {
        public bool member;
    }

    void Start ()
    {
        MeshFilter nullObject = null;
        MeshFilter nonExisting = GetComponent<MeshFilter>();
        Transform existing = GetComponent<Transform>();
        MeshFilter defaultObject = default(MeshFilter);
        TestClass classInstance = new TestClass();
        TestClass nullClassInstance = null;

        Debug.Log("---- Actual null");
        Debug.Log(nullObject);                                      // Null
        Debug.Log(nullObject == null);                              // True
        Debug.Log(nullObject is null);                              // True
        Debug.Log(nullObject is object);                            // False
        Debug.Log(nullObject ? "Is Not null" : "Is Null");          // Is Null
        Debug.Log(_ = nullObject as object ?? "Was null");          // Was null
        Debug.Log(object.ReferenceEquals(nullObject, null));        // True

        Debug.Log("---- Non-existing");
        Debug.Log(nonExisting);                                     // null
        Debug.Log(nonExisting == null);                             // True
        Debug.Log(nonExisting is null);                             // False  (!!!)
        Debug.Log(nonExisting is object);                           // True   (!!!)
        Debug.Log(nonExisting ? "Is Not null" : "Is Null");         // Is Null
        Debug.Log(_ = nonExisting as object ?? "Was null");         // null   (!!!)
        Debug.Log(object.ReferenceEquals(nonExisting, null));       // False  (!!!)

        Debug.Log("---- Existing");
        Debug.Log(existing);                                        // GameObject (UnityEngine.Transform)
        Debug.Log(existing == null);                                // False
        Debug.Log(existing is null);                                // False
        Debug.Log(existing is object);                              // True
        Debug.Log(existing ? "Is Not null" : "Is Null");            // Is Not Null
        Debug.Log(_ = existing as object ?? "Was null");            // GameObject (UnityEngine.Transform)
        Debug.Log(object.ReferenceEquals(existing, null));          // False

        Debug.Log("---- Default");
        Debug.Log(defaultObject);                                   // Null
        Debug.Log(defaultObject == null);                           // True
        Debug.Log(defaultObject is null);                           // True
        Debug.Log(defaultObject is object);                         // False
        Debug.Log(defaultObject ? "Is Not null" : "Is Null");       // Is Null
        Debug.Log(_ = defaultObject as object ?? "Was null");       // Was null
        Debug.Log(object.ReferenceEquals(defaultObject, null));     // True

        Debug.Log("---- Non-Unity.Object");
        Debug.Log(classInstance);                                   // NullTest+TestClass
        Debug.Log(classInstance == null);                           // False
        Debug.Log(classInstance is null);                           // False
        Debug.Log(classInstance is object);                         // True
        // Debug.Log(classInstance ? "Is Not null" : "Is Null");    // Compile error
        Debug.Log(_ = classInstance as object ?? "Was null");       // NullTest+TestClass
        Debug.Log(object.ReferenceEquals(classInstance, null));     // False

        Debug.Log("---- Null Non-Unity.Object");
        Debug.Log(nullClassInstance);                                   // Null
        Debug.Log(nullClassInstance == null);                           // True
        Debug.Log(nullClassInstance is null);                           // True
        Debug.Log(nullClassInstance is object);                         // False
        // Debug.Log(nullClassInstance ? "Is Not null" : "Is Null");    // Compile error
        Debug.Log(_ = nullClassInstance as object ?? "Was null");       // Was null
        Debug.Log(object.ReferenceEquals(nullClassInstance, null));     // True
    }
}
1 Like


My comment wasn't about GetComponent but UnityEngine.Objects in general. GetComponent does only return fake null object when testing in the editor, not in the actual build. When you build your game GetComponent will return null if the component doesn't exist. Of course that doesn't help much when testing in the editor.

I agree that the explicit creation of a fake null object when the component does not exist was one of the worst decisions. Though most people who hit a missingReference exception do silently appreciate the indication which object caused it. This would not be possible when you hit a normal NullReferenceException.


That's even worse, as it directly leads to inconsistent behaviors between the Editor and the build.

It's surely my fault, but I can't see how this case (a fake null object returned by GetComponent) can be of any help. You call GetComponent(). TypeOfComponent doesn't exists. Returns a fake null. That fake null is tried to get accessed later and raises an exception in the line that tried to use the fake null object. So there, in that line, you can see the problem. Which additional information comes in the fake null object that is so useful for this? Am I missing something evident?

WOW, welcome back to your thread Edy... I dunno what the forum rules say about necro-ing your own post to reply three years later, but well, here you are!!

I do know this however: almost everything about Unity3D example code, developer mindset, tutorials, and everything else relies critically on this quirky "special" null-boolean-true-whatever-you-wanna-call-it handling that Unity implemented long ago.

Was it smart? It had a point at the time to allow people to write C# code more like Javascript or Python code.

Is it going to change? Perhaps, but I wouldn't count on it anytime soon.

My company employs about 60 engineers who use this stuff daily... and we use it successfully. I cannot even think of the last time anybody even skipped a beat working with it. We certainly never have any hard bugs about it.

We recognize this quirk as an important part of how Unity works.

It actually doesn't MATTER that it "isn't right." It simply IS.

Therefore I urge you not to let it slow you down, not to waste your life agonizing about it.

Understand how to use it, adapt your thinking to it, and soon you'll find that it is really quite easy.

It might even help you to think "This isn't C# but rather UnityC#."

Have fun making games! That's what it's all about after all.

1 Like

Holy necro!

If you call Debug.Log(message) and then double-click the message in the console, you get nothing but the call stack.

If you call Debug.Log(message, object) and then double-click the message in the console, the object which was given will be "pinged" in the hierarchy, as well as getting the call stack.

If you cause a C# Null Reference Exception, the underlying C# error message results in a call of the first form. You have the call stack, and that's all.

If you cause Unity's Unassigned Reference Exception, the message uses the second form, with the component which actually has the reference field that needs to be filled in. The hierarchy will ping the gameobject that has a "Missing" reference.

3 Likes


This is why we use TryGetComponent<T>(out T component), which allows for cleaner code while providing a real null object, and not a fake null.

Pretty much no reason to use GetComponent<T> these days.

2 Likes


Well, the benefit is minimal. Though to quote the blog post which explains that:
[quote]
it allows us to store information in the fake null object that gives you more contextual information when you invoke a method on it, or when you ask the object for a property. Without this trick, you would only get a NullReferenceException, a stack trace, but you would have no idea which GameObject had the MonoBehaviour that had the field that was null. With this trick, we can highlight the GameObject in the inspector, and can also give you more direction: "looks like you are accessing a non initialised field in this MonoBehaviour over here, use the inspector to make the field point to something".
[/quote]

2 Likes

Thing is that I had received a notification related to this thread, and I assumed that the latest reply was just added. Didn't check the date, so I just replied to it :sweat_smile:


Didn't know about that! That's really useful info indeed.

I am horrified, absolutely horrified by this entire thread. I thought that JavaScript's insane handling of null, undefined, falsity, == vs ===, and so forth was a horrible abomination. This is worse—so, so much worse. Whoever made this decision at Unity was wrong, was badly wrong, made not merely a foolish mistake but an inexcusable, horrific blunder, and must be legally barred from approaching software design to a distance not less than ten miles. There is no excuse: it's horrible. It's profoundly horrible. It's dumb. Dumb.

Now, since TryGetComponent<T> is long-winded tedium (though adequate in such desperate circumstances), what better way to verify component existence have people found?

I just—in thirty years of game development I have never seen anything so stupid as a null type that is not null and for which == null is false. Incredible.


Just another opinion. It was already agreed that it wasn't a great design choice. Though you haven't suggested an actual alternative. When interfacing with native code you always have to make some compromises. Also keep in mind that at the time Unity was developed UnityScript (which was a .NET lanugage that had a javascript like syntax) was the mainly used scripting language. So looking at it from a pure C# point of view is pretty misplaced. Also back then the ONLY operator in C# that was affected was the ?? operator. None of the other non overloadable magic direct null check operators did exist back then.

Note that this is not only about the return value of a method that does not find an object. What about this:

GameObject go = new GameObject("new");
DestroyImmediate(go);

// use "go" here

What would be your suggested pattern here to avoid issues with the now destroyed native object and the dead wrapper? We discussed alternatives here. What is your complaint exactly? As already mentioned, removing the operator overload DOES NOT CHANGE THE FACT that managed objects can become dead / unusable because the native part has been destroyed. How would you like to check for both: if a reference is not null and can still be used?

To me your response sounds like someone who hasn't really grasp the actual issue that they solved with this "hacky" solution.

ps: You could complain about the design of C# which does not allow to overload the behaviour of the new "is null", "?.", "?[ ]" and all the other fancy operators the language provides. It makes it impossible to handle this case properly. So all those operators go into the trash and you always have to combine a null check with a separate "isAlive" check. That would be the ONLY workaraound.

5 Likes