Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Variable May or May Not be Null: Conflicting Evidence

Discussion in 'Scripting' started by SecondDegreePerm, May 1, 2020.

  1. SecondDegreePerm

    SecondDegreePerm

    Joined:
    Jul 2, 2017
    Posts:
    42
    Hi,

    I am having a curious issue where immediately after I invoke a constructor and assign it to a variable, there is conflicting information as to whether that value is or is not null. See the code below:

    Code (CSharp):
    1.         cornerTraverser = new CornerTraverser();
    2.         if (cornerTraverser == null) {
    3.             Debug.Log("cornerTraverser is null");
    4.         }
    5.         Debug.Log("prove your existence: " + cornerTraverser.ToString());
    6.         Debug.Log("prove your existence: " + cornerTraverser.prove());
    7.         Debug.Log("assigning corner traverser: " + cornerTraverser);
    I have also put a "Debug.Log" statement in the constructor of CornerTraverser. The prove method simply returns a string. The output is as follows:

    upload_2020-5-1_17-51-24.png

    My question is this: what on earth is happening here? I am able to invoke methods as though the object is instantiated, but other checks suggest the variable holds a null value and not an instance of CornerTraverser. Any insight would be much appreciated.

    Thanks in Advance
     
    Last edited: May 1, 2020
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    What is CornerTraverser? Does it inherit from MonoBehaviour, or any other UnityEngine.Object type?

    If so you can not use its constructor, and instead must use 'AddComponent' for components, and 'CreateInstance' for non-components (scriptableobjects).

    The reason is that these types have both a managed object (your CornerTraverser class), and an unmanaged native object on the internal side. By calling AddComponent/CreateInstance, you signal to Unity to create the unmanaged native object, and when it's done, Unity then also creates the C# managed object and associates it via the 'instanceid' and returns it to you.

    The reason it evaluates null is because Unity has overloaded the "== null" operation to return true if no unmanaged native object exists. Basically it takes the instanceid and checks if that instanceid exists. Since you created the managed object on your own, the instanceid = 0, and well... that instanceid always never exists.

    The reason for the overloaded == operation is so that way you can tell if an object has been destroyed using the 'Destroy' method. Because when that method is called the unmanaged native object is purged from memory... but the C# managed object still exists and will continue to exist until the GC clears it. But unfortunately the GC won't do so until you drop all references to it. So in the mean time, if something is referencing it somewhere, it needs to know if it's been destroyed or not.

    Unity opted for == null to determine this way back in the early days of Unity. In more recent years the argument that this is kind of bad design as it's potentially confusing have caused Unity to reassess it. But unfortunately it's a technical debt they can't easily undo without wrecking support across the board in Unity. So in it stays.

    ...

    Exact insight of your line by line...
    2-4: it evaluated == null as true because no unmanaged native object exists
    5: the ToString method returns a null string when it has no unmanaged native object
    6: your 'prove' method is a member of the managed object and therefore works
    7: appending non-string objects to a string just implicitly calls the ToString method... so this is same as line 5
     
    Last edited: May 1, 2020
  3. SecondDegreePerm

    SecondDegreePerm

    Joined:
    Jul 2, 2017
    Posts:
    42
    Ah of course! Completely forgot about that, the "__ == null" behaviour completely threw me off, alongside the methods actually being invoked! That makes complete sense why that was happening now, I've used AddComponent now and everything seems to line up with what I would expect. Thanks for the incredibly quick and insightful respons!