aaaaaaaaaaa
You canāt set a Transform, and Iām not just talking about a constructor, you CANāT, not even to null
do you mean Transform Parent =null; is wrong?
Yeah, you can only fetch already existing transforms
Iām confused about you said, why setter is forbidden?
Take this for example: gun = player.transform.Find("Gun").gameObject;
(source: https://docs.unity3d.com/ScriptReference/Transform.Find.html
This is fetching. Look at the example, it never ever sets a transform to null
@qklxtlx , youāre not confused, @gorbit99 is. āTransform Parent = nullā is perfectly fine. Transform references can be null, like any other object reference.
The problem is probably related to the way Unity has overridden .Equals in UnityEngine.Object, to report that destroyed objects are equal to null. This affects == and != but maybe doesnāt affect ??.
Iāve tried to find some documentation on this, but so far, have found nothing thatās clear. Wikipedia claims that ?? and != null are equivalent, but Iām skeptical. The C# docs, which are generally written very carefully, say that ?? returns the left operand if it āis not nullā ā it doesnāt say āis not equal to null.ā This implies that ?? is only testing whether the reference is actually null, not some other value that maybe Equals null.
EDIT: Yeah, compare to the docs on ==, which go on at some length about how this might be overridden. I think itās pretty clear now that ?? does no such thing, and is basically checking ReferenceEquals(lhs, null).
What I still donāt get, though, is why making your Parent reference a public field rather than a local variable has anything to do with it!
First of all, heās not trying to set a transform, heās only referencing one.
You surely cannot set a gameobjectās transform, thatās just the nature of the gameobject. They have to have a transform and itāll stay the same all the time.
That specific operator only takes the usual ānullā into account, whereas Unity also offers a pseudo-null object behaviour to present more information to the programmers in the editor (thatās what happens in the second example, itāll set such a mysterious object to that non-assigned, serialized field).
Usual comparisons like ==, != and some boolean comparisons will check whether the variable is ānullā or an instance that behaves as if it was null.
You can show the difference by simply treating the reference in the second example as UnityEngine.Object and then as System.Object.
Hereās a small snippet:
public class NullTest : MonoBehaviour
{
public Transform Parent;
void Start()
{
// overriden operator compares to null and fake-null, result is true
Debug.Log(Parent == null);
// casting to a System.Object reveals that there's actually something assigned to it, result is false
Debug.Log((System.Object)Parent == null);
}
}
Thanks, @Suddoha . That was the bit I was missing ā I thought an unassigned public field would actually be null, but I see that Unity instead assigns (as you say) fake-null.
I often wonder what the guy at Unity who came up with the idea of the null override thinks of his decision this many years later.
I can relate to that, because itās a little tricky and can be really confusing!
GameObjects and all sorts of components (afaik) are treated like that, but not MonoBehaviours. Thatās probably the thing which I also sometimes forget.
Even more confusing: generics.
Take this example:
public class NullTest : MonoBehaviour
{
public UnityEngine.Object obj;
public GameObject go;
public Component component; // transform, light, camera, etc.
public MonoBehaviour behaviour; // usually custom types
public Component Parent;
void Start()
{
LogNullResult<UnityEngine.Object>(obj);
LogNullResult<GameObject>(go);
LogNullResult<Component>(component);
LogNullResult<MonoBehaviour>(behaviour);
}
private void LogNullResult<T>(T obj)
{
var typeSpecific = obj == null;
var asSystemObject = (System.Object)obj == null;
Debug.Log(string.Format("Result for {0}: Type-Specific: {1}, System.Object: {2}", typeof(T), typeSpecific, asSystemObject));
}
}
Afterwards, add a constraint, so replace
private void LogNullResult<T>(T obj)
(which is, as you probably know, kind of equal to āwhere T : System.Objectā)
with
private void LogNullResult<T>(T obj) where T : UnityEngine.Object
Clearly a difference that might have caused headaches and bugs sometimes.
Without constraint it takes the System.Objects implementation (which is logical, as operators are defined as statics), with the constraint it takes the operator of the constraining type. What a mess and pain sometimes. :S
Got it, Thank you very much for the detailed discussion and explanations!
Iāve never thought of fake null before in fact ![]()
just another question: the inspector may show āmissingā or ānoneā for object field, is this included in the fake null also
Thatās just indicating that thereās nothing set, with the difference, that āMissingā additionally indicates there was something which does no longer exist and None, it hasnāt been set at all or explicitly been set to None.
āNoneā may cause an āUnassignedReferenceExceptionā whereas āMissingā may cause a āMissingReferenceExceptionā, but only in the context of types that are actually candidates for Unityās fake-null. Or not? See below!
Yet again, missing references seem to only āreallyā appear during runtime, if it says missing in the editor while not in play mode, it will be ānoneā as soon as it starts. At least thatās what Iāve observed, you may have observed something else.
Anyway, if you explicitly set the field to null at some point, itāll be just the normal null and will not act as if it was null.
Also, local variables will not act as null even for those types mentioned above. Itās only for GameObjects and Components (except MonoBehaviours) that are serializable fields.
But hereās a fun fact: Destruction with Destroy(ā¦), it changes the game:
If you destroy a MonoBehaviour, itāll behave like a component and cause the null-check to yield true, while (of course) a cast to System.Object yields false. (Youāre still referencing something which āshould notā exist anymore). So thatās another inconsistent behaviourā¦
It all has something to do with the lifetime of objects in C++ and C#.
If you destroy the object, the unmanaged C++ object can be destroyed, its memory can be released but the C#-sided object will remain at least until you lose the last reference to that object so that it can be collected.
At that point, they somehow wanted to still be able to tell you that it āshould be nullā (but cannot be null - C# stuff).
The best is always to check something by yourself, I donāt know all the little details either and I donāt wanna spread wrong information. ![]()
Yes, I would assume so.
So basically just remember this: if youāre using == or != to compare Unity object references to null, this will work both for references that are actually null, and for references that are equivalent to null (like destroyed objects, and apparently serialized fields that have no value assigned). But if you use ?? or ReferenceEquals, this tells you whether your reference is actually null (only). As @Suddoha showed, you can also do the latter by typecasting to System.Object.
So, in most cases the behavior of == and != is quite convenient⦠itās almost (but not quite) like having magic references that automatically get set to null when an object is destroyed. But yeah, it can be a gotcha sometimes!
Sounds like they regret it. https://blogs.unity3d.com/2014/05/16/custom-operator-should-we-keep-it/
However the functionality is so core to the engine that itās difficult to remove without breaking every single project ever built. At the same time itās likely that we would simply be trading one edge case for another.
Here are my rules for working around it.
- Never check if a UnityEngine.Object is null directly, always use the bool operator
- Never cast a UnityEngine.Object to a System.Object
- Never write a generic method designed to take UnityEngine.Object and System.Object
I suppose I can add never use ?? on a UnityEngine.Object to the list too.
And once we get the C# 6 update, we also wonāt be able to use the ?. operator. ![]()
Pretty much. Unless the operator can be overridden to work with the Unity fake null.
Note that most of the proposed solutions for removing the fake null and equals override would still leave you with the destroyed objects problem. Itās not easily possible to set up a memory managed environment that also allows you to destroy arbitrary objects.
I thought the ?. operator was equivalent to == null?
Nope, see the post literally right above the one you responded to. ![]()