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. Dismiss Notice

Different types of null?

Discussion in 'Scripting' started by RandomComm3nt, Sep 23, 2018.

Thread Status:
Not open for further replies.
  1. RandomComm3nt

    RandomComm3nt

    Joined:
    Feb 2, 2016
    Posts:
    6
    So this is driving me insane and making me question my understanding of C#. I have an Assert.IsNull validation that is failing in certain cases and telling me that the value is "null" but not "Null".

    Assertion:
    Code (CSharp):
    1.         public void Initialize(Table table, Card card)
    2.         {
    3.             this.table = table;
    4.             Card = card;
    5.             Debug.Log(card.CardRenderer);
    6.             Assert.IsNull(card.CardRenderer, "Attempted to render a card that already had a CardDisplay");
    7.             card.CardRenderer = this;
    8.             cardImage.sprite = card.CardData.Image;
    9.         }
    CardRenderer property:
    Code (CSharp):
    1.         public ICardRenderer CardRenderer
    2.         {
    3.             get
    4.             {
    5.                 return _cardRenderer;
    6.             }
    7.             set
    8.             {
    9.                 _cardRenderer = value;
    10.             }
    11.         }
    Where ICardRenderer is an interface (which I think is probably the issue)

    For my particular test, the Initialize method runs twice. Here's the output:

    upload_2018-9-23_22-42-27.png

    So the first case passes because the value is "Null" but the second one fails with "null".

    Viewing the values in Visual Studio doesn't help. For the first case:

    upload_2018-9-23_22-40-5.png

    For the second case:

    upload_2018-9-23_22-44-0.png

    This seems really strange - how does a "null" reference have variables, some of which have been set? What does this mean?

    Few other points to note, the breakpoint on the setter is never triggered for the second object and there are no other references to the underlying variable _cardRenderer in the code.

    I would really appreciate anyone who can shine a light on this! Let me know if you need any more information.
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Unity is weird...

    So, what's going on is that anything that inherits from UnityEngine.Object technically has 2 parts to it. There is the Unity C++ object, and there is the mono/.Net object.

    The .net/mono object is in managed code and as long as the object is referenced it exists. The Unity C++ object can be destroyed in unmanaged code in any number of ways such asObject.Destroy, or because a scene unloaded, etc.

    If the Unity C++ object is destroyed, yet the mono/.Net object exists still, you still can't access it. Something like say Transform.position reaches into the Unity C++ object, but nothing is there, so an error will occur.

    So we need a way to determine if an object was destroyed.

    How does Unity do this?

    Well this is where it gets annoying. Unity overrides the == operator on UnityEngine.Object to check both if it's null OR if it's been destroyed.

    BUT, due to the way the C# compiler works. This only works if your variable is typed as a UnityEngine.Object or its child classes. If you instead have it typed as say 'System.Object', or as an interface (like in your examples). This overload does not work. And since the C#/mono/.net object still technically exists it doesn't resolve as being null. Note that the debugger still shows "null" or "Null" (in quotes) when you inspect them because well, Unity also wrote it to do that as well (the capital N is funny enough a nice hint to that). I wish they'd changed it to something like "Native Destroyed" or "Native Null" or something.

    Years ago we as a community complained about this, but Unity decided to maintain this behaviour.

    How I personally work around it is that I use the 'System.Object.ReferenceEquals' method to test if something is ACTUALLY null:
    Code (csharp):
    1. if(object.ReferenceEquals(myObj, null))
    If you then need to check if it's destroyed make sure it's typed as UnityEngine.Object and use the == operator.

    The other option to testing if it's destroyed is to uncover the internal function from Unity to do the test. Which sucks because it's not public and therefore needs to be reflected. You can see how I reflect it here in my ObjUtil static class... funny thing is that it's not a 'IsDestroyed' function, but rather a 'IsNativeObjectAlive' (it's in the static constructor where I create a delegate for it which is then returned as a property later in the class):
    https://github.com/lordofduct/space...ter/SpacepuppyUnityFramework/Utils/ObjUtil.cs


    [edit]

    OH YEAH

    I should also tack on that there is yet another quark in the unity API.

    And that is some methods return special 'Null' objects for things. Like the legacy animation system does for animation states that don't necessarily exist. And they too override == to equate to null, but actually aren't null.

    I couldn't tell you the full list of functions that do this. But they've bit me in the butt a few times when writing generalized/abstracted code using object/interfaces.
     
    Last edited: Sep 23, 2018
    Mehlian, Bunny83, equal and 3 others like this.
  3. RandomComm3nt

    RandomComm3nt

    Joined:
    Feb 2, 2016
    Posts:
    6
    Thanks for the detailed answer, in my case the solution was to clear the references to the object in the OnDestroyed method.
     
  4. chriscode

    chriscode

    Joined:
    Mar 2, 2015
    Posts:
    44

    Very necro: but I recently found that thing = GetComponent<Whatever>() returns "Null" if the component isn't there. So ReferenceEquals(thing,null) returns false! And then you check with == null and then it IS "Null". BUT that's not the worst of it because GetComponentInChildren<Whatever>() - which is basically the same if the component you're looking for is on the same gameobject, returns proper null! So ReferenceEquals(thing,null) is true!

    I MEAN WTF!
     
    Last edited: May 21, 2023
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,713
    It's pretty straightforward rules for C# and absolute rivers of digital ink have been spilled on the subject of Unity's overloading of the boolean operator.

    If I may propose an alternate strategy than ALL CAPS, you may wish to familiarize yourself with how Unity works in this regard.

    I work every single day of the week with over 50 other engineers, day in and day out in Unity, and these null details never even come up. It's just how things work in Unity. We know it, we code for it. Full stop.
     
    orionsyndrome, Bunny83 and Ryiah like this.
Thread Status:
Not open for further replies.