Why is passing this array causing some of its values to become null?

So, I’ve worked in c# for a few years and I’ve never seen anything like this and it’s got me stumped. Basically I have a generic method that returns an array. It shouldn’t return any null values. In fact, right before it returns the array, I have it set to test if any values are null and print out a debug if they are. See:

 public T[] Get ( int x, int y )
    		{
    			T[] test = GetRange(x,y,x,y);
    
    			foreach ( T t in test )
    			{
    				if ( t == null ) { Debug.Log("yo"); }
    			}
    
    			return test;
    		}

And then after that I use the result from this function like so:

public Tile[] GetTilesAtPosition ( Vector3 tilePosition )
		{
			//Cache position variables
			int getX = Mathf.RoundToInt(tilePosition.x);
			int getY = Mathf.RoundToInt(tilePosition.y);

			//Get the Tiles at the desired position
			Tile[] test = currentTiles.Get (getX, getY);

			foreach ( Tile t in test )
			{
				if ( t == null ) { Debug.Log("YO"); }
			}

			return test;
		}

Even though I JUST checked all of the values in this array to see if any of them were null, and none of them were, now that I’ve passed it over to this function, testing it again DOES say some are null. So, “yo” is never printed but “YO” is, even though nothing should have changed between the two. Is there any reason something like this could happen?

I see several possible problems here. First of all what kind of classes you have in your array? MonoBehaviours or other Unity objects? Or do you use it for your own classes which aren’t derived from a Unity class?

You should use “class” as generic constraint if you want it to be a nullable type.

public class YourClass<T> where T : class
{
    public T[] Get ( int x, int y ) 
    {
        //...
    }
}

I assumed you have a generic class since a generic method wouldn’t make much sense since you call “GetRange” in there without any generic parameter.

If your Tile class is derived from MonoBehaviour or ScriptableObject, it’s possible that the generic null test returns “not null” but the explicit null test returns “null”.

This is because Unity overloads the == operator and the Equals method to “fake” that an object is null if it’s native counter part has been destroyed or doesn’t exist. They might even remove the overload in the future.

The problem here is that the generic method uses System.Object’s == operator since it doesn’t know the type beforehand. The == operator is not a virtual method which can be overridden.

See this example:

public Collider c; // assigned in the inspector

void Start()
{
    DestroyImmediate(c);
    
    // executes the overloaded == operator. This "if" will be executed
    if (c == null) 
        Debug.Log("c is Null");
    
    object o = c; // copy the reference to a System.Object variable
    // executes the System.Object's == operator. This "if" will not be executed since the object sill exists
    if (o == null) 
        Debug.Log("o is Null");
}

Unity objects become “fake null” if:

  • the object got destroyed, either with Destroy / DestroyImmediate or when a new scene is loaded.
  • you create an object derived from MonoBehaviour or ScriptableObject with it’s constructor manually instead using AddComponent / CreateInstance.

Please refer to @Bunny83’s solution since my solution is mostly based on his corrections

For unconstrained generic types, you can’t test if the value is null since you don’t know if the type is nullable.

You have 2 solutions, depends of what you try to achieve.
First is to constraint your generic type to be derived of a nullable type, such as Object:

public T[] Get<T> (int x, int y) where T : class
{
    // [...]
}

Another solution is to check for the default value instead a null value:

foreach (T t in test)
    if (IsNullOrFakeNull(t))
        Debug.Log("yo");

public static bool IsNullOrFakeNull(object aObj)
{
    return aObj == null || aObj.Equals(null);
}

[Edit]
Edited the second solution to avoid boxing

[Edit]

  • Changed the first solution constraint to “class”

The “class” constraint just limits the use to reference types.

  • Added @Bunny83’s fake null test in the second solution.