Home > Enterprise >  Unity/C# null reference exception isn't present, but should be
Unity/C# null reference exception isn't present, but should be

Time:03-28

Program falls into this branch and still there is no crush, which is very unexpected. Object is destroyed and null, however all the data is still the same. For my understanding it should crush with null reference exception. Am I missing some general info about how null works in c# or i ecnountered some known bug in unity/dotnet?

if (begining == null)
{
    print(begining.marked);
    foreach (var near in begining.nears)
    {
        print(near);
    }
    print(begining!.distance);
    print(begining.nears_amount);
    print(begining.prev);
}

beggining is object of type Node:

public class Node : MonoBehaviour
{
    public int nears_amount = 3;
    public readonly HashSet<Node> nears = new HashSet<Node>();
    public float distance = Single.PositiveInfinity;
    public Node? prev = null;
    public bool marked = false;
    public void connect(Node another);
    private void OnDrawGizmos();
    public void disconnect_all();
    private void onm ouseOver();
    public static bool BFS(Node start, Node seeked);
    public static List<Node> Dijkstra(Node start, int depth = -1);
    public static List<Node> DFS(Node current, int depth);
}

debugger: enter image description here

CodePudding user response:

Long story short: In general for things inheriting from UnityEngine.Object rather use the bool operator when checking for existence

if (begining)
{
    print(begining.marked);
    foreach (var near in begining.nears)
    {
        print(near);
    }
    print(begining.distance);
    print(begining.nears_amount);
    print(begining.prev);
}

Note that Unity has a Custom == implementation (see also further explanation here) for anything inheriting from UnityEngine.Object.

This custom == is basically be based on

static bool CompareBaseObjects(UnityEngine.Object lhs, UnityEngine.Object rhs)
{
    bool lhsNull = ((object)lhs) == null;
    bool rhsNull = ((object)rhs) == null;

    if (rhsNull && lhsNull) return true;

    if (rhsNull) return !IsNativeObjectAlive(lhs);
    if (lhsNull) return !IsNativeObjectAlive(rhs);

    return lhs.m_InstanceID == rhs.m_InstanceID;
}

While the underlying System.Object (= object) might actually not be null on the c# layer, this custom == additionally checks if the underlying instance on the c layer exists or was destroyed (IsNativeObjectAlive).

Note how Unity doesn't show NullReferenceException but rather a custom MissingReferenceException indicating that this System.Object is actually not really null but it is rather the Unity operator == that returns true for null, even if the System.Object still exists.

This happens as soon as you destroyed an object and then try to access it in the same frame (or better said before GC collects that instance and makes this System.Object actually null).

So in general: Do not use == null but rather the bool operator for checking for existence (even though the Unity API often uses == null themselves unfortunately).

Even further: Also never use ?., ?? / ??= or !. operators for UnityEngine.Objetc since they operate on the underlying System.Object as well and ignore the custom == and Equals implementation of UnityEngine.Object!

  • Related