Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How am I misusing the null-coalescing operator? Is this evaluating "null" correctly?

I am trying to use the null-coalescing operator in a C# script in Unity, my project Scripting Runtime is set to .NET 4.x so it should work correctly.

The problem is that even though the LEFT operand evaluates to null, it does not correctly return the RIGHT operand.

Here is an example statement that does NOT work when the left Operand returns null:

m_meshFilter = ( GetMeshFilter() ?? AddMeshFilter() );

Here is the SAME exact statement, except it explicitly passes null to the null-coalescing operator (this works correctly when run)

m_meshFilter = ( GetMeshFilter() == null ? null : GetMeshFilter() ) ?? AddMeshFilter();

Here is a short script to easily test the problem directly in Unity.

using UnityEngine;

public class DoubleQuestionTest : MonoBehaviour
{
    public GameObject myGo;

    public MeshFilter m_meshFilter = null;

    [ContextMenu( "Test1 Null Coalescing Operator" )]
    void Test1()
    {
        m_meshFilter = ( GetMeshFilter() ?? AddMeshFilter() );

        if ( m_meshFilter == null )
        {
            Debug.Log( "m_meshFilter was null, trying Alternate" );
            m_meshFilter = ( GetMeshFilter() == null ? null : GetMeshFilter() ) ?? AddMeshFilter();
        }
    }

    MeshFilter GetMeshFilter()
    {
        MeshFilter temp = myGo.GetComponent<MeshFilter>();

        if ( temp == null )
            Debug.Log( "    > Get Mesh Filter RETURNING NULL" );

        return temp;
    }

    MeshFilter AddMeshFilter()
    {
        Debug.Log( "    > Add Mesh Filter Called" );
        return myGo.AddComponent<MeshFilter>();
    }
}

You can run the test function by clicking the top right corner of the Component in the Inspector in Unity (2018.3.12f1)

When using the test script, the first use of the Null Coalescing operator will fail, but the second succeeds when I check for null explicitly, and then redundantly pass null (with the ternary operator)

Output in Unity Editor Console:

Image Output!

Is this a bug? or am I doing something wrong?

_______________________________

EDIT:

In looking for a workaround I found a decent way to make return REAL null for the sake of the null-coalescing operator:

    public static T GetComponentRealNull<T>( this GameObject self ) where T : Component
    {
        T component = self.GetComponent<T>();

        if ( component == null )
            return null;

        return component;
    }

Or for more specific cases of adding/getting components:

    public static T GetComponentOrAddIfMissing<T>(this GameObject self) where T : Component
    {
        T component = self.GetComponent<T>();
        if(component == null)
            component = self.AddComponent<T>();

        return component;
    }
like image 371
prime2scope Avatar asked Jul 03 '19 18:07

prime2scope


People also ask

Why we use null coalescing operator?

operator is known as Null-coalescing operator. It will return the value of its left-hand operand if it is not null. If it is null, then it will evaluate the right-hand operand and returns its result. Or if the left-hand operand evaluates to non-null, then it does not evaluate its right-hand operand.

Can I use null coalescing operator?

Null-coalescing Operator Use this operator to fall back on a given value. In cases where a statement could return null, the null-coalescing operator can be used to ensure a reasonable value gets returned. This code returns the name of an item or the default name if the item is null.

Which is null coalescing operator?

The nullish coalescing operator ( ?? ) is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined , and otherwise returns its left-hand side operand.

Why should Unity objects not use null propagation?

Object is destroyed, even if the object itself isn't actually null. Null propagation cannot be overridden in this way, and therefore behaves inconsistently with the == operator, because it checks for null in a different way.


1 Answers

This is Unity screwing with you.

If you do this:

MeshFilter GetMeshFilter()
{
    MeshFilter temp = myGo.GetComponent<MeshFilter>();

    if ( temp == null ) {
        Debug.Log( "    > Get Mesh Filter RETURNING NULL" );
        return null;
    }
    return temp;
}

It works.

Why?

Because Unity has overridden Equals (and the == operator) on all Unity objects so that destroyed game objects and never existing objects are both "equal" to null (this was done in an attempt to make developer's lives easier). But a destroyed (or "missing") object is not literally null: its a wrapper object in the C# part of the engine pointing at a null object in the underlying C++ code. The null coalescing operator checks for literally null.

For example try this:

Start() {
    GameObject gg = new GameObject(); //create a GO
    DestroyImmediate(gg); //destroy it immediately
    Debug.Log(gg == null); //prints true: it is definitely null!
    GameObject go = gg ?? this.gameObject; //get a non-null object
    Debug.Log(go); //prints null
}

This is also why you get a MissingReferenceException when you try to access Unity objects that are null, rather than a NullReferenceException: those objects aren't literally null, but only effectively null.


In general that's what the Object.bool operator is used for. Just prefer to use it instead of a null check:

public static T GetComponentOrAddIfMissing<T>(this GameObject self) where T : Component
{
    T component = self.GetComponent<T>();

    if(!component) component = self.AddComponent<T>();

    return component;
}
like image 136
Draco18s no longer trusts SE Avatar answered Oct 08 '22 23:10

Draco18s no longer trusts SE