- Home /
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?
$$anonymous$$aybe we could see the GetRange() function? it might the one returning null values. Try to do a better Debug.Log to get more relevant information on the tiles. Also use for() loop it will be less confusing and check that both array have the same length from one function to another.
Answer by Bunny83 · Jul 07, 2015 at 12:48 PM
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.
Wow, thanks for such a detailed answer! You were right about my use of the 'fake null' check. So, I've rewritten the generic class to only contain subclasses of one of my monobehaviors, so this check will work there. $$anonymous$$y code's all working now, yay! I'll keep in $$anonymous$$d that unity overloads the == operator like this, this actually explains a few problems I've had in the past now that I think about it. Next time I write a generic class like this I'll remember this! Thanks so much again!!
Thank you for the clarifications, seems I was a bit confused because I didn't knew about this operator overload. @dustxx : you should mark this solution as the "anwser" since it is better answer than $$anonymous$$e.
Done! Your answer was also very helpful, thanks again @$$anonymous$$outon
Answer by Mouton · Jul 07, 2015 at 11:13 AM
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.
It's still hard to say without knowing what GetRange() does, but it really necessary to use generics here? $$anonymous$$y gut feeling, seeing as the Get() function is so simple, is that you could make things a lot easier for yourself by just writing a version of that function that's specific to Tiles.
I edited the solution, please use
if (t.Equals(default(T)))
ins$$anonymous$$d
@$$anonymous$$outon:
That makes no sense. Your "null check" only works for Unity's fake null objects. For any real null reference this will throw a null-reference exception. The Equals method is an instance method.
I just want to stress that: Do not use Equals for null checks, ever.
@Bunny83 Why is it a bad idea to use "Object" as a generic constraint ?
Will this not work in Unity ?
if (EqualityComparer<T>.Default.Equals(t, default(T)))
{
Debug.Log ("Not null");
}
@$$anonymous$$outon:
I never said you couldn't use Object as generic constraint. Btw you didn't mention if you ment UnityEngine.Object or System.Object. System.Object can't be used as constraint since everything is compatible with it the compiler doesn't allow it.
When choosing a constraint you always limit the useage of a generic class / method and on the other hand provide more functionality inside the generic class / method since the type is more specific. A constraint should always be as general as possible and as restrictive as necessary.
From what we know the generic parameter is used with a type called "Tile" and this seems to be a reference type since he wants a null check (which is pointless for value types). However we don't know from which class it might be derived from. The "class" constraint just limits the use to reference types.
About your second question: EqualityComparer uses Object.Equals unless the class in question implements IEquatable<T>
which none of the Unity classes do.
However it seems that $$anonymous$$ono's implementation is slightly different from microsofts. This is the Equals method of the default comparer for object types:
// microsoft's implementation
public override bool Equals(T x, T y)
{
if (x != null)
{
return y != null && x.Equals(y);
}
return y == null;
}
// $$anonymous$$ono's implementation
public override bool Equals(T x, T y)
{
if (x == null)
{
return y == null;
}
return x.Equals(y);
}
At first glance they look equal except that they have reversed logic. However $$anonymous$$ono has one null check less. In the case where it uses Equals it doesn't check y for null which would allow to catch Fake-null objects, but only when you pass the reference first and "null" last. If you reverse the two parameters it doesn't work anymore ^^
Btw System.Object.Equals is implemented like this:
public static bool Equals(object objA, object objB)
{
return objA == objB || (objA != null && objB != null && objA.Equals(objB));
}
For Unity classes if you want reliably catch if they are fake null or null you first have to do a normal null check and then when you're sure that it's not null, use Equals. If that returns true it's "fake null".
I posted this somewhere else already. You can use something like this:
public static bool IsNullOrFakeNull(object aObj)
{
return aObj == null || aObj.Equals(null);
}
This will return true even for fake null objects.