Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Unity 2017 and null conditional operators

Discussion in 'Experimental Scripting Previews' started by MNNoxMortem, Aug 21, 2017.

  1. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Is there any update on the support of null conditional operators and null coalescing operators in Unity 2017 with experimental .NET 4.6 support?

    I am talking about the ?? and ?. operators. I am fully aware you should not use them for any Unity class but all posts about those lie way back in time and I would really hope unity will provide support for them.

    Are they already supported? Will they ever be (are there current plans, have the plans changed, is it plain impossible because of the backing c++ objects?)
     
  2. lukaszunity

    lukaszunity

    Administrator

    Joined:
    Jun 11, 2014
    Posts:
    461
    We have not made a final decision on this yet. This is on our radar and we will share the details once a decision has been made.
     
  3. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    the problem is that a null Unity Object is not always null:
    - it is null if you assign null to it (or get it from some API that actually return null)
    - it is a "fake null" object reference if it's a public field in the editor
    - it is an invalid (but not null) reference if it's a destroyed object
    - [other cases I have not considered?]

    UnityEngine.Object then overrides the == operator to make a "fake null" object to appear as it were null,
    but the ?? and ?. operator are not overridable in c#, and they check for null in the strict sense (i.e. the reference is actually ReferenceEquals to null).
    making them use the overridden == operator would be a breaking change in the language.

    the only option would be to make the operator themselves overridable in a future version of c# (or a unity mod to the specs, but I hope they don't).
    right now there are overridable "operator true" and "operator false" that are used in short-circuiting && and || for custom types (they were used for DB interop if I remember right)
    maybe a new "operator null" could be added that modifies the ?? and ?. behaviour (it would still break code that relies on the difference between real and fake null, if there is any out there, but that can be changes to a direct object.ReferenceEquals call)

    in my code I have an extension method as a workaround, like:
    Code (CSharp):
    1. public static T RealNull<T>(this UnityEngine.Object obj) {
    2.     return obj ? obj : null;
    3. }
    (using the implicit bool conversion)
    then use it like
    Code (CSharp):
    1. var finalObj = myObj.RealNull() ?? defaultObj;
     
    MNNoxMortem, MEGA64 and Arkade like this.
  4. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Am I missing something. Should your extension method work as written? The compiler tells me for your extension method: "Cannot convert expression type UnityEngine.Object" to type T.

    Haven't written extension methods myself so far.
     
  5. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    no it was just an example written on the fly. it should take a T as parameter, constrained on UnityEngine.Object.
    fixed version:
    Code (CSharp):
    1. public static T RealNull<T>(this T obj)
    2.     where T : UnityEngine.Object
    3. {
    4.     return obj ? obj : null;
    5. }
    (this time I compiled it)

    ps: never copy/paste/execute code from the internet without understanding it :p
     
    MNNoxMortem likes this.
  6. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Haha, I didn't. I wanted to see if it compiles and make some tests on it, especially for something that important as breaking code because of ?? and null checks on unity objects :D

    I was aware that it requires the type constraint on UnityEngine.Object, but thought the static class would provide it (which is not possible as I know now) . As I said I have not written extension methods so far but used them and read the MSDN documentation about them.

    Long story short: Thank you for providing it and that you took the time to write that detailed explanation! I don't think I will use it, but I ALWAYS want to understand it, and test it, and see what it does :D Even if I maybe won't use it to use ?? now, I learned a lot about it and extension methods!
     
  7. Qbit86

    Qbit86

    Joined:
    Sep 2, 2013
    Posts:
    487
    Semantics of null conditional operators absolutely must not be changed just to correspond initial design flaws in Unity. Unity just made a huge mistake going against standard recommendations for implementing equality:
    https://msdn.microsoft.com/en-us/library/336aedhh.aspx «x.Equals(null) returns false.»
    https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/operator-overloads «The semantics of the operator need to be compatible with several other members, such as System.Object.Equals.»

    Please just do not do even worse by hacking «?.» Elvis operator.
     
  8. Vanamerax

    Vanamerax

    Joined:
    Jan 12, 2012
    Posts:
    938
    I'm inclined to agree with @Qbit86 here that taking the equality hack further to the elvis operators is just getting deeper into the rabbit hole. I would much prefer Unity to align with the C# design standards than to sacrifice the standard for a little easier syntax focussed at beginners.
     
    PhilSA and Qbit86 like this.
  9. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    See the discussion in the last thread about this.

    To me, it comes down to changing the semantics, which gives this:

    Code (csharp):
    1. someObj?.Foo();
    Keeping everything as-is, which gives this:

    Code (csharp):
    1. if(someObj != null) someObj.Foo();
    Or ditching the overload, which gives this:

    Code (csharp):
    1. if(someObj != null && !someObj.isDestroyed) someObj.Foo();
    2.  
    3. //or alternatively:
    4.  
    5. someObj.Checked()?.Foo();
    In the last instances, the majority of the text is dealing with the possibility of null. That's garbage. The interesting part of the code that I need to read is SomeObj.Foo().

    I'd love for all three to be options we can set. If it can be set per-assembly, the assembly defines from 2017.2 removes the issue of incompatibility with asset store packages and plugins.

    If choice is out of the picture, I'd prefer having the semantics that's easiest to read and matches Unity the best.
     
    Last edited: Sep 1, 2017
  10. MechEthan

    MechEthan

    Joined:
    Mar 23, 2016
    Posts:
    166
    Been coding C# in Unity for a year now and I didn't even know this custom == was a thing :(
    This issue is kind of horrifying... what's the best practice here even? I'm extremely used to relying on null to indicate state.

    Also, from here: https://blogs.unity3d.com/2014/05/16/custom-operator-should-we-keep-it/
    • The custom ==operator is not thread safe, so you cannot compare objects off the main thread. (this one we could fix).
    Was this ever fixed?
     
    Last edited: Sep 22, 2017
    nat42 likes this.
  11. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    You really shouldn't be working with UnityEngine.Object objects (the ones the overridden == are used for) off the main thread. As far as I understand it, those objects all have a version living on the c++ side of the engine, and the two versions kinda have to be in sync. Unity assumes that they only change during a frame.

    In my experience, you only really run into this when using interfaces, because that's the instance where you'll be working with a UnityEngine.Object in a context where it's not types as a UntiyEngine.Object. If you don't have a lot of interfaces floating around, it's probably not an issue.
     
  12. tylerw-savatronix

    tylerw-savatronix

    Joined:
    Nov 10, 2013
    Posts:
    90
    Well...crap.

    (I've got LOTS of interfaces on account of my love for Pure DI as well as encapsulation/separation of concerns).
     
  13. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    Here's an extension method I use to work around it:

    Code (csharp):
    1. /// <summary>
    2. /// Checks if an object either
    3. /// - is null
    4. /// or
    5. /// - is a UnityEngine.Object that has been destroyed.
    6. ///
    7. /// Unity overloads the == operator for UnityEngine.Object, and returns true for a == null both if a is null, or if
    8. /// it doesn't exist in the c++ engine. This method is for checking for either of those being the case
    9. /// for objects that are not neccessarilly UnityEngine.Objects. This is usefull when you're using interfaces, since ==
    10. /// is a static method, so if you check if a member of an interface == null, it will hit the default C# == check instead
    11. /// of the overridden Unity check.
    12. /// </summary>
    13. /// <param name="obj">Object to check</param>
    14. /// <returns>True if the object is null, or if it's a UnityEngine.Object that has been destroyed</returns>
    15. public static bool IsNullOrUnityNull(this object obj) {
    16.     if (obj == null) {
    17.         return true;
    18.     }
    19.  
    20.     if (obj is UnityEngine.Object) {
    21.         if (((UnityEngine.Object) obj) == null) {
    22.             return true;
    23.         }
    24.     }
    25.     return false;
    26. }
     
  14. tylerw-savatronix

    tylerw-savatronix

    Joined:
    Nov 10, 2013
    Posts:
    90
    Thanks. I'm not sure if I'll end up using that or not, because a lot of my classes have a requirement that they can never touch/know about the Unity API (hence my extensive use of interfaces). Those are (in the not too distant future) going to be chucked into a separate DLL with no references to Unity so this code would end up breaking when I do that >.<

    We decided to take an approach where as much game logic as possible is put into standard C# classes instead of monobehaviors. We're treating Unity like a front end layer, with the bulk of the games logic being considered a mid or back end layer, e.g. CharacterComponent has a Character, and when Unity events/interactions happen the component just calls into Character to tell it what to do, and is subscribed to events on Character to react to things that happen to Character that need to be handled in the Unity layer.

    I'll need to check and see if/how often we've got a monobehavior implementing one of those interfaces for those cases where one of our classes needs to interact with something that has to be a monobehavior. >.< But it sounds like I may well end up having to implement a crap ton of additional exception handling which would probably take less time than a full architectural overhaul (which I probably wouldn't do anyways because I "need" DI).
     
    Qbit86 likes this.
  15. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    you can still put
    Code (CSharp):
    1. if (this == null) return;
    or
    Code (CSharp):
    1. if (!this) return;
    whenever a UnityEngine.Object is implementing an interface. or be careful about their lifetime
     
  16. Qbit86

    Qbit86

    Joined:
    Sep 2, 2013
    Posts:
    487
    Agree, the less Unity in Unity game — the better. Ideally, no Unity at all, with all their broken, non-idiomatic APIs.

    Just do not implement your interfaces by MonoBehaviours — use aggregation instead. Wrapper classes are “normal” in sense of checks via Elvis operator, and they provide `Alive` property, which performs “null check” for referenced MonoBehaviour.
     
    Energy0124 likes this.
  17. ImperativeGames

    ImperativeGames

    Joined:
    Dec 11, 2016
    Posts:
    34
    If Unity overriding == really hurts performance I think it should be changed because performance is issue #1 with Unity, for me at least.
     
  18. nat42

    nat42

    Joined:
    Jun 10, 2017
    Posts:
    353
    Is there a beginner's tutorial that covers this stuff that I missed? :O
     
  19. ImperativeGames

    ImperativeGames

    Joined:
    Dec 11, 2016
    Posts:
    34
    Unity tutorials are mostly for "novice" programmers, who doesn't know that stuff like overriding operators exists... Read the forums, I guess.
     
  20. nat42

    nat42

    Joined:
    Jun 10, 2017
    Posts:
    353
    Yeah, you're telling me... anyway randomly finding 'quirks' on a forum and in unexplained notes on documentation (that people swear is to be ignored), naturally should make anyone a little nervous about what they don't know. Don't want to be an expert, but want to have enough of a sense of this stuff that I could know if I hit an issue with it or when to search for answers.
     
  21. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    I am completly with you, but Unity is full of little quirks and stuff you need to know to work with it on larger scale. You will quickly stumble into most of them, something will break, you will search here on the forum, and you will learn more about the engine.

    I can recommend the Unity Game Developer streams and/or videos on youtube to see how other people work with unity and you will quickly see, that many people adopted a quite different style working with unity than they would working in a different engine.

    Let's face it, it is not that any other Engine behaves differently looking at those "quirks". All Engines try to provide a swiss-army-knife and sometimes this is simply not the best way of doing it, but one working for most workflows :)

    Also the manual is not that bad. Sometimes the information is a bit scattered over manual, documentation and tutorial videos or unity talks (check the videos of unity guys and girls at unite on youtube)

    Welcome in the world of Unity development.
     
    Last edited: Nov 5, 2017
  22. Enderlook

    Enderlook

    Joined:
    Dec 4, 2018
    Posts:
    53
    ... 3 years later. What happened with that decision? I notice there is still a custom == operator (due the warning got by VS when using Unity 2019). Has been this decision declined or still on hold? Thanks
     
  23. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    They kept the old behavior.

    I've got a lot fewer problems with it then I used to back in 2017 - mostly because I think I got more pragmatic over the years, and because tools (especially Rider) is really good at catching potential mistakes due to the implementation.
     
  24. Enderlook

    Enderlook

    Joined:
    Dec 4, 2018
    Posts:
    53
    Oh, did they addressed the performance and multithreading issues at least?
     
  25. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    It never had any substantial performance problems. The multithreading issues has nothing to do with the == operator - you can't interact meaningfully with UnityEngine.Object types outside the main thread anyway.

    If the minor perf overhead or multithreading issues are dealbreakers for you, you should look into DOTS, where neither of these things are relevant. There's no objects there, so == null is just not going to be a thing you check ever.
     
    MNNoxMortem likes this.
  26. Enderlook

    Enderlook

    Joined:
    Dec 4, 2018
    Posts:
    53
    Ok, thanks
     
  27. MikeWise

    MikeWise

    Joined:
    Dec 27, 2017
    Posts:
    14
    Can you expand on "meaningfully"? Obviously you can calculate something and then assign it to a property of a UnityEngine.Object in a threadsafe manner, so this isn't absolutely true. What did you have in mind?
     
  28. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    Yeah, I was a bit hyperbolic. Sure you can assign data or call methods on eg. Components, as long as you make sure that it doesn't touch any of the properties that visit the c++ engine side of things.
     
  29. LucidSloth

    LucidSloth

    Joined:
    Mar 9, 2021
    Posts:
    5
    @Baste Could I ask you, do you still use this extension helper to this day when developing in Unity.
     
  30. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    I don't use it very often, no. Looking through our codebase, it's only used in utility methods, in order to handle unconstrained type parameters, since those can be interfaces. An example is:
    Code (csharp):
    1. /// <summary>
    2. /// Checks if this GameObject has a component attached.
    3. /// </summary>
    4. /// <typeparam name="T">Type of the component to look for</typeparam>
    5. /// <returns>True if the component was found</returns>
    6. public static bool HasComponent<T>(this GameObject gameObject) {
    7.     return !gameObject.GetComponent<T>().IsNullOrUnityNull();
    8. }

    In practice, I use HasBeenDestroyed more often, which checks if something is destroyed rather than just was never assigned.
    Code (csharp):
    1. public static bool HasBeenDestroyed(this Object obj) {
    2.     if (ReferenceEquals(obj, null)) {
    3.         return false;
    4.     }
    5.     return obj == null;
    6. }
    I use that to be clear in code that I expect things to be destroyed, instead of just never having been assigned, or in debug code that should show different output between unassigned and destroyed objects.
     
    LucidSloth likes this.