简体   繁体   中英

GetComponent returning "null" instead of null

I'm confused about the return value of GetComponent if the requested component is not attached to the object. According to the Unity documentation, GetComponent should return null. However, what appears to be happening is that GetComponent is returning a "null" object of the requested type, rather than the value null .

In this example, my game object does not have a CircleCollider2D attached to it. When I set a breakpoint on the line CircleCollider2D x = GetComponent<CircleCollider2D>();, I get this result

Why is the returned value not null ?

EDIT:

Here's a full screenshot of the code and the values in the debugger.

ANOTHER EDIT:

Could it be that Unity has overloaded the == operator so that GetComponent always returns a object, but the object can have an internal "null" state which returns true when compared to null ? I can see the following declarations in the UnityEngine namespace

public static bool operator ==(Object x, Object y);
public static bool operator !=(Object x, Object y);

It seems like GetComponent<T>() doesnt return TRUE null . Instead it returns new T with default values that fires MissingComponentException when using any null field. GetInstanceID() and GetHashCode() work because they only use int m_InstanceID which is set to default 0. Not sure how ToString() works but it probably returns "null" when m_InstanceID == 0 .

Proof:

void Start()
    {
        CircleCollider2D getComponent = GetComponent<CircleCollider2D>();
        CircleCollider2D empty = null;
        CircleCollider2D newCC = new CircleCollider2D();
        Debug.LogFormat("getComponent.GetInstanceID() {0}", getComponent.GetInstanceID());
        Debug.LogFormat("newCC.GetInstanceID() {0}", newCC.GetInstanceID());
        try
        {
            Debug.LogFormat("empty.GetInstanceID() {0}", empty.GetInstanceID());
        }
        catch (System.NullReferenceException e)
        {
            Debug.Log("empty.GetInstanceID() doesnt work, im true null");
        }
        try
        {
            string t = getComponent.name;
        }
        catch (System.Exception e)
        {
            Debug.Log(string.Format("getComponent fires {0} when any field is null", 
                e.ToString()));
        }
        try
        {
            string t = newCC.name;
        }
        catch (System.Exception e)
        {
            Debug.Log(string.Format("newCC fires {0} when any field is null",
                e.ToString()));
        }
    }

Results:

getComponent.GetInstanceID() 0
newCC.GetInstanceID() 0
empty.GetInstanceID() doesnt work, im true null
getComponent fires UnityEngine.MissingComponentException
newCC fires System.NullReferenceException

Also:

getComponent.GetHashCode() = 0
getComponent.ToString() = "null"

Why getComponent == null is true? Usually it's just:

`getComponent.GetInstanceID() == otherComponent.GetInstanceID()`

In o == null case it's:

return !(
o.GetCachedPtr() != IntPtr.Zero || (!(o is MonoBehaviour) && 
!(o is ScriptableObject) &&
Object.DoesObjectWithInstanceIDExist(o.GetInstanceID())));

So i guess object with InstanceID = 0 never exists. Search for decompiled UnityEngine/UnityEngine/Object.cs if u want to know more.

Could it be that Unity has overloaded the == operator

Tldr: Yes .

Why is the returned value not null?

According Custom == operator, should we keep it? :

When a MonoBehaviour has fields, in the editor only, we do not set those fields to “real null”, but to a “fake null” object.

Why? According that article, basically for two reasons:

  • On Editor mode: get more contextual information on invocations, so if you have an exception you will be able to know which object was the cause.

  • Every that comes from UnityEngine.Object are wrapped in a C# object to keep the reference to the Unity C/C++ GameEngine Object. In the editor/code we work with the C# elements but those are just a reference to the 'real' c++ objects. Now, the lifetime of those 'real' c++ objects are handled explicitly (if you destroy something the C++ object it will be destroyed and the allocated memory it will be freed) but the C# object will be completely destroyed when the Garbage Collector decides to free that memory. So you will have a not null C# object pointing to a null C++ object. With this fake null trick you will know that this specific C# object is null because in the C++ side it was destroyed.


This is also important when if you have a singleton and if (sm_Instance == null) is not working as expected, you can use: if (EqualityComparer<T>.Default.Equals(sm_Instance, null)) instad.

Edit: Probably this can be caused if your singleton use generics and your type constrain is class

public class Singleton<T> where T : class {

This will cause to use the == operator of a class object, even when your Singleton is a GameEngine Object. One fix for that? Instead of class you can use UnityEngine.Object , like:

public class Singleton<T> where T : UnityEngine.Object {

Related Thread:
https://forum.unity3d.com/threads/null-check-inconsistency-c.220649/#post-1775701


Ps: If you go to UnityEngine.Object assembly, you will be able to see the overload:

UnityEngine.Object

Almost at the end...

超载

if you are still looking for solution this worked for me`

var component = parent.GetChild(i).GetComponent<T>();
if(component != null)
{
    if (component.ToString().Equals("null") == false)
    {
        list.Add(component);
    }
}

GetComponent is expected to return object of type 'Component . Hence it is not expected to return "null" rather ~null . Hence it is not expected to return "null" rather ~null is expected value. Perhaps this strange behavior should be reported to unity3d ,

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM