This is supposed to become a discussion, which mainly stems from the thread [Unity Future .NET Development Status]( Unity Future .NET Development Status page-2#post-7055404) (I’ve linked the comment that sparked that conversation)
Now that thread and the team partaking in it (from my understanding) would not be the ones performing that transition, if it may ever happen. Because of that I hope to get some more people from the Unity Team on this thread.
UnityEngine.Object is the base class of most Objects used in Unity (like MonoBehaviour, Rigidbody, everything deriving from Component). It also overloads the ‘==’ operator, because a reference might point to a GameObject that already has been destroyed - a change that cannot be represented by simple nullability. Therefore destroyed GameObjects enter a state of fake null, which would otherwise not be equal to null.
This means that the root of the problem is that fake null state, which could be removed if a UnityEngine.Object.IsDestroyed property (or something similar) would be provided.
Some points that speak for this change (some are also mentioned in the 2014 Blog post):
null checking with ‘is’-keyword (from C# 6) or ‘is not’ (from C# 9) has long been the preferred way
null coalescing and other operations would be possible
overall more readability and less repetition
framework change has already major breaking changes
One thing to consider: the main points against this change might not be known to us.
To summarize and kick off the discussion, here some questions to the Unity Team, but also the users:
Would it even be possible to have these breaking changes made at the beneficial time during the framework upgrade?
Why was there no follow-up to the blog post from 2014 (which was named Custom == operator, should we keep it?), after its question was answered mostly with ‘no’, also by the team?
How can we help the Unity Team to achieve this goal?
Is there something I missed (in which case I’ll gladly edit this introduction)?
If they do make this change, I’d like some method which replicates the original behavior. Most of the time when I check if an object is null, what I really want to know is if it is null or destroyed. Probably 99% of the time that’s what I want to know. If there isn’t a method added which does the same thing as the current behavior, I’ll end up writing some static method I bring into every project which just takes the place of this everywhere:
if ((SomeObject == null) || SomeObject.isDestroyed)
The overloaded == is a relic of the past that has the perfect time be done away with when Unity finally migrates off mono to RyuJIT.
With that said there are 5 different ways to we could check if the Unity.Object is destroyed or null. Having a property to do it makes it too error prone, because then you would need to check if the reference is null as well as destroyed. That’s why the member functions calls below are extension methods so we don’t throw on null.
My personal vote is C or E, because it follows the C# naming scheme with string.IsNullOrEmpty(). I think both being valid isn’t a terrible way to handle it either to keep most people happy. Just E isn’t awful either if it feels weird to exploit that extension method don’t throw on null.
In theory, I think the ?? and ?. operators could be made to “work” even without changing the == behavior, just by removing the fake-nulls for things other than destroyed objects. Although having == check for destroyed objects while ?? and ?. check only for true nulls does seem kind of confusing, so this wouldn’t be a no-brainer.
On the topic of a conversion tool to automatically update an existing script if == changes: It seems obvious that such a tool couldn’t tell the difference between old code (which needs to be updated) and new code (which needs to not be updated). However, at the time of any transition, there will presumably be a large number of people with code bases that are 100% old code that needs to be updated; it seems like it should be possible to write a tool that would handle that? It maybe shouldn’t be run automatically to avoid mistakes, but having a tool that could be manually invoked by a human developer who knows that the project is 100% old code seems like it would make the transition a lot less painful.
In terms of downsides, note that many Unity tutorials will suddenly stop working for reasons that will be entirely inscrutable to newbies. Also, many third-party assets/plugins that are added to your project as source code (rather than being precompiled) will presumably generate issues that will be hard for anyone other than the third-party author to fix. (Though I guess the solution to that last one is to very carefully check the date of the latest version and then manually invoke the conversion tool on just that asset.)
Even if you don’t change the behavior of ==, making a public “isDestroyed” property still seems like a good idea. I can imagine situations where you’d want to treat a destroyed object differently from a true null, and exposing this seems essentially harmless to anyone who doesn’t need it.
I think it’s pretty simple, the api updater should kick in and convert any and all Unity.Object comparisons to null to whatever the new api is. DLLs will need to be recompiled anyways when we move off mono, so that’s why it’s a perfect time.
I can’t really think of a time in my code base where I care only if the Unity.Object is just null over null or destroyed. Which actually is a good argument that maybe the better solution to all of this is to work with Microsoft to be able to overload the ? And ?? operators and keep the == operator overloading.
It’s not just explicit comparisons to null, but also comparisons to other objects. (Maybe both objects are different fake-nulls, or one is a fake-null and the other is a true null.)
Checking for destruction does carry a performance cost, and I have frequent situations where a variable might be null but there’s no risk it will be destroyed (e.g. the result of a Find, or kinds of object that just never get destroyed, or when the object holding the reference would also be destroyed by anything that destroyed the referenced object).
I suppose Object.ReferenceEquals is still an option. Of course, that misses out on the syntactic benefits of ?. and ??
Even if you could overload == and ?? and ?. and “is null” and “is not null” to all check for Unity fake-nulls, there’s still a confusing case where you have a reference to a Unity object stored in a non-Unity-typed variable and so it doesn’t invoke the overload. (This is already true of the current == overload.)
Good point I just ran into this bug recently for our serialization system where everything is cast to c# object.
But yes there are also the performance benefits of not having to cross over to native code when checking if it’s null if you know it will never be destroyed.
I’m 100% on board we need to get rid of fake null, == overload, and implicit bool conversion. Also I don’t really care how the api is implemented because worse case I’ll make an extension method with how I think it should look lol.
Be aware that serialized Unity Object fields often have their values set to “fake null” instead of actual null when deserialized, so it’s not only destroyed objects you need to worry about.
The problem with changing this is that it introduces extensive breakage on the majority of Unity projects. Null checking is ubiquitous on most Unity code, be it games, asset store plugins, and other 3rd party libraries.
The worst is that most of the bugs would only manifest at runtime, when all of sudden random null checks will start returning true instead of false. It would be a QA nightmare to get a codebase migrated into this new situation.
Since we are already talking about stuff that won’t happen in the next year, could nullable references be a solution, in your opinion? This way, Unity could just prevent playmode if non-nullable references are not set.
Nope, the issue is that if you just declare a public field in a MonoBehaviour, its value is not a null C# reference, but a reference to a fake null UnityEngine.Object (because Unity’s serializer cannot serializer actual null values). There are other Unity APIs which return fake nulls as well.
That would need to be fixed/changed first, otherwise you would need to write even more code and still wouldn’t be able to safely use null coalescing since you’d need to call IsValid() or whatever everywhere, simply making your null checking more verbose.
In my opinion this change would be far too disruptive just so people can write slightly shorter code. Maybe it could be made optional (if such a thing as disabling an operator overload from a precompiled assembly is even possible), but I’m afraid adoption could be even worse than fast domain reload, due to the much higher affected code coverage.
And in the end you’d still need to be careful when using the null coalescing operator on objects that could be destroyed, but made every null check as equaly brittle too.
Think about it: there is no reason you’d want your code to operate on a destroyed object, as that will always raise an exception. Removing the fake null overloads do nothing other than increase the number of places in your code that can go wrong.
Unfortunately there is no perfect way around this due to the very nature of C#: game objects are inherently more like disposable resources, like streams and file handles, which can be invalidated while still being referenced elsewhere.
I actually just hit this when trying out GitHub - louthy/language-ext: C# functional language extensions - a base class library for functional programming. Specifically using the Option type with a public accessor results in some undesired behavior (Some(null) instead of None). I do see the point that adding another slightly different way to check for a uninitialized unity object most likely has big implications. Although not knowing this distinction when encountering a bug is pretty frustrating. Perhaps just shouting out this more clearly in docs is sufficient, but having an opt in for real nulls only would be awesome.
This always seemed like a purity argument to me: C# has optional ways of checking for null that won’t work in Unity, which is somehow bad. But all that matters is Unity has one simple, obvious null-check that works just great. Maybe someone could write up how Unity is following DRY or something official-sounding? I suppose I’ve also seen this come up with someone just learning to program, following terrible advice, wondering why “target = currentTarget ?? getNewTarget();” doesn’t work.
I just want to clarify something here that some people seem to forget here. Yes, Unity could have implemented some kind of IsAlive method to check if a gameobject is still alive and leave the == operator alone. However if they get rid of the custom == operator NOTHING will actually change in regard to all the other null operators like ?? .? “is null” and so on.
The issue with dead objects is still there and won’t magically go away. Apart from the custom == operator, people complain about the intentionally returned fake-null objects. First of all this behaviour does only exist when testing inside the Unity editor. At runtime methods like GetComponent, FindObjectOfType, … will actually return null, though in some cases they could still return actual dead objects which just got deleted this frame.
Yes, I agree that deliberately returning a fake null object is pretty pointless. According to the blog post they did this to give a “better error message”. To me that’s a horrible reason, especially all they did is replacing a NullReferenceException with a MissingComponentException. It has little to no value. So getting rid of thie behaviour would be great as in my opinion it has little to no value.
Though the fact that Unity returns fake null objects in certain situations is a seperate topic from the custom == operator. Again, dead / “fake null” objects will always exist. When the == operator is removed, it will just fail to detect those objects as “dead” so you always have to do an additional check. So the == operator is there just for convenience.
The custom == operator works great. The only people running into issues are mainly those, who work heavily with interfaces where the custom operator does not apply. But as I said, removing the operator would have no effect at all on the issue. The “normal” null check will not detect fake null / dead objects since they are actual objects. Since interfaces have no relation to UnityEngine.Object, how would you do an actual “alive” check on an interface without some kind of cast to UnityEngine.Object? The answer would be the same as it is at the moment. You could create an extention method for System.Object that does the alive check
public static bool IsDeadOrNull(this object aObj)
{
return aObj == null || (aObj as UnityEngine.Object) == null;
}
public static bool IsAlive(this object aObj)
{
if (aObj == null)
return false;
if (aObj is UnityEngine.Object uObj)
return uObj != null;
return true;
}
Those work with any kind of object and will treat dead Unity objects properly. If Unity would remove the == operator you would still need to use something similar to those, just replace the Unity == operator with the dedicated IsAlive method that Unity hopefull will provide in the future.
I totally agree with @Bunny83 .
As for me UnityEngine.Object null check looks kinda weird after using C# outside Unity, but it’s ok and not a problem by itself.
What I really don’t like it’s fake null for GetComponent, not set serialized field and other. Like @Bunny83 already said it has different behavior in Editor and Player and that’s make my very unhappy
. I made extension method ToNullable just to convert fake nulls to real nulls after each GetComponent/ FindObjectOfType and on each serializable field that could be null by my script contract to get rid of fakes. And on pull request reviews we check if this method called to make sure all scripts follow this pattern.
public static class UnityObjectExt
{
[return:MaybeNull]
public static T ToNullable<T>([AllowNull] this T @object)
where T : class
=> (@object is UnityEngine.Object unityObject && unityObject == null)
? null
: @object;
}
I like to treat UnityEngine.Object instances like IDisposable classes: you can use it safely while it was not destroyed/disposed, but after disposing/destroying some methods could work and some don’t.
And like with disposable when you get disposable instance in your method (don’t matter how exactly) you expect that it ready to work. So basically you could have two-ish states of object: valid object that you could use or null. Getting not valid object as input means that there some bug in caller code becouse it gives you not valid object.
But with UnityObjects you can’t expect the same. Serializable field could be invalid object but not null becouse editor inject it, or some method return it becouse didn’t find valid object instead of just give you null as a result. That’s why we use ToNullable method just to “fix” this expectation and align UnityEngine.Object behavior closer to IDisposable.
About casting UnityEngine.Object to interfaces I don’t believe it’s huge problem with good design. If your reference to interface lives longer that object behind it than you have some bug in your code. And again this is similar to IDisposable usage. If your method or class works with disposable object and in middle of it object gets disposed not by this method/class than you have some lifetime management issue. Unlike unity objects most disposable objects doesn’t have any method to check if it was disposed and in most cases it’s ok becouse you should know if it valid to use or not without additional check.
So in short, I think the biggest problem is fake nulls in field and from GetComponent and not zombie objects(destroyed objects) or overriden equality operator.
On the topic of “better error messages” for things like GetComponent, I have to wonder is it even necessary for the fake-null object to exist for this purpose?
I.E: If the intent was to replace a NullReferenceException with a MissingComponentException, for instance, why can’t that just be done like this?
public T GetComponent<T>()
{
T component = /*Whatever logic to get the component*/*;
if(component == null)
{
throw new MissingComponentException();
}
return component;
}
Because the error is not, or should not, be thrown inside GetComponent but when you try using the result without checking it. GetComponent should not throw an exception if the component you’re looking for does not exist. Always keep in mind that you should not use exception handling as a tool for normal control flow. GetComponent is also the only way to “test” if a certain component exist or not. So the exception we talk about would be raised after GetComponent when you try to use the null / fake null object:
// does not throw an exception
var c = collider.GetComponent<MyComponent>();
// will throw an exception if component does not exist.
var go = c.SomeMethod();
That’s where they will throw a missing reference exception instead of a NRE. To me a NRE would even make more sense because there are two reasons for the exception:
Either the object we are looking for is actually missing. So the developer forgot to add the component or somehow destroyed it previously
the other reason is that the code is actually wrong and should be fixed.
Exceptions represent an unexpected state which makes it impossible to continue normally. A MissingComponentExcpetion implies that the code is correct but you’re somehow missing a component. A NRE is actually more versatile and makes no assumptions about the cause. So the issue could be that you forget to add the component, but it’s also possible that you have faulty code. So you may need / want to add a null check. In either case you have to investigate and analyse the cause. The MissingReferenceException essentially becomes a synonym for an NRE and doesn’t really give any additional information besides “it’s null but it’s a UnityEngine.Object derived type that is null”. They tried to give more suggestions in the error message what “may” be the cause to help out newbies. However history has shown that newbies that do not understand what a NRE is do not really understand what a MRE means. In fact those suggestions often lead to wrong assumptions. Similar to people who do not know the proper C# syntax and follow the instructions of the compiler error literally. Like “; expected” and they just add the semicolon which in many cases does not really fix the issue because the error was something else.
Regarding Bunny83’s example, I’m thinking back: when Unity started, “UnityScipt” was the simplified preferred language. The target was game designers using equally simplified custom game-design languages in other engines. Null Reference Exceptions has always been more confusing – Forums and Answers has always gotten lots of Q’s about them and it takes a while to explain why spelling a name wrong gives such a funny error. It’s entirely possible that converting “null ref exception” into the nicer “missing component error” is one of the things that got early Unity over the hump of acceptance.
Please Unity, look into removing the overloaded == operator. C# and .Net moves on, into a direction with new features that build upon true null, and it would be terrible to simply not be able to use any of those. Even now it feels like we’re once again stuck in time while things move on without us. We know that this will break certain behavior but I am certain that it is worth it and would be appreciated by all of the community. You yourself asked the community if you should remove it, in 2014, which the vast majority answered with ‘yes’. Now almost 8 years later, it is definitely time to act.
Things that do not work as long as the overload exists:
null-coalescing
pattern matching
switch expressions that check for null
nullable reference types (half / half)