Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Null capitalization

Discussion in 'Scripting' started by OutDoorsScene, Sep 1, 2023.

  1. OutDoorsScene

    OutDoorsScene

    Joined:
    Sep 9, 2022
    Posts:
    140
    What is the difference between a uppercase and lowercase null?
     
  2. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,834
    Not sure where you're seeing these.

    The word
    null
    , all lowercase, is a C# fundamental constant meaning an unfulfilled reference, a reference to nothing. If you tried to access anything through such a reference, you would get a Null Reference Exception. In the Unity Inspector, fields which are references will show the word "None (type of reference)" instead of "null" or "Null".

    Sometimes some coders will make a variable or an enum value called Null, but the meaning there would be on a case by case basis.
     
    Yoreki and spiney199 like this.
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Yeah I've never seen a capitalised Null anywhere in C# land, and no amount of googling is turning up a result.

    It might be a thing in C/C++ from what I can see, but not at all used in C#.
     
  4. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,834
    Other languages use similar words for the same concept:

    * C: NULL, a pointer without type to memory address zero, which is not a valid allocation
    * C++: NULL, the number zero, which when treated as a pointer, points to no valid allocation
    * Python: None, a reference to nothing
    * Perl: undef, a reference to nothing
    * LISP: nil, a reference to nothing

    And so on.

    Interestingly, and I know little about these languages,

    * Rust, Haskell: no first-order syntactic concept of null
     
    wideeyenow_unity and spiney199 like this.
  5. OutDoorsScene

    OutDoorsScene

    Joined:
    Sep 9, 2022
    Posts:
    140
    The uppercase Null happens when trying to get a gameobject in a different scene and the lowercase null happens when trying to get a gameobject that's in the same scene but hasn't been assigned.
    Code (CSharp):
    1. public static class DefaultStaticScene
    2. {
    3.  
    4.     [RuntimeInitializeOnLoadMethod]
    5.     static void StartDefaultScene()
    6.     {
    7.         Debug.Log(PlayerTAG.GetPlayer());
    8.     }
    9. }
    10.  
    11.  
    12. public class PlayerTAG : MonoBehaviour
    13. {
    14.  
    15.     static GameObject player;
    16.  
    17.     private void Awake()
    18.     {
    19.         player = GetComponent<GameObject>();
    20.     }
    21.  
    22.     public static GameObject GetPlayer()
    23.     {
    24.         return player;
    25.     }
    26.  
    27. }
     
    Last edited: Sep 1, 2023
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    You mean in the Console??? That would've been useful to know.

    Probably nothing. Just someone in Unity wrote "Null" and someone wrote "null" in different parts of Unity's code base.
     
    wideeyenow_unity likes this.
  7. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    Are you referring to the error code warnings? If you just mean the text in the errors, that has nothing to do with anything. I too am unfamiliar with any particular "Null" or even "NULL", all I know is "null".
     
  8. OutDoorsScene

    OutDoorsScene

    Joined:
    Sep 9, 2022
    Posts:
    140
    I'm referring to
    Debug.Log()
    which only says "null" or "Null" depending on how the object is null.
     
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Probably means nothing. Maybe the difference between an actual null value and a fake null Unity object, or just some quirk of Unity's particular runtime environment it uses.

    In either case, it's still just a null value. That's all that really matters. Not the capitalisation.
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    It's just the string representation as you're logging it to the console.

    As for why they're different is todo with how the object gets stringified.

    Technically there is a minor/slightly difference between them that you sort of aught to be aware of. And that is the fact that there is 3 main types of null unity objects you might run into.

    1) True null objects - this is the null that results from the standard C#:
    object obj = null;

    2) Destroyed unity object - this is an object that did exist, but has since been destroyed. The object on the unity side of things doesn't exist, but the managed C# object still does exist. The object actually isn't null, but if you do "== null" it'll return true because unity overrides the == operator to equate null if it's destroyed.

    3) Editor time only dummy null object - if you have a serialized field that isn't assigned to anything, when unity deserializes it doesn't actually set it null. Instead it sets it to a fake object similar to the 'destroyed unity object' in that it equates == null, but actually has a C# managed object attached. But every member of it throws an exception if you access it.

    Basically if you have this:
    Code (csharp):
    1.     public class zTest02 : MonoBehaviour
    2.     {
    3.  
    4.         public GameObject obj;
    5.  
    6.         private void Start()
    7.         {
    8.             object truenull = null;
    9.             Debug.Log(truenull);
    10.  
    11.             Debug.Log(object.ReferenceEquals(obj, null));
    12.             Debug.Log(obj == null);
    13.             Debug.Log(obj);
    14.             Debug.Log(obj.transform.position);
    15.         }
    16.  
    17.     }
    But leave it configured null like this:
    upload_2023-9-1_0-52-8.png

    You'll get this as an output:
    upload_2023-9-1_0-53-4.png

    Note:

    The truenull object prints "Null".

    The fake null object on the other hand does:
    1) returns false when compared to actual null via object.ReferenceEquals, because there actually is a C# managed object in existence.
    2) returns true when compared to null via == operator, becuase Unity overrides the == operator to return true if the object is destroyed or if it's this fake null object in the editor
    3) this specific fake null object prints as lowercase "null"... this printing is just unity stringifying the object and doing so as lower-case. Why? Cause that's how whoever wrote the 'ToString' method wrote it. Where as whoever wrote the true null stringify did it "Null" (the answer to your original question)
    4) Lastly you'll notice that the exception thrown when trying to access the fake null object isn't actually a "NullReferenceException" but instead is an "UnassignedReferenceException" that explicitly tells you what variable on what script was not assigned.

    Note that also if I had this in my Start instead:
    Code (csharp):
    1.             DestroyImmediate(this);
    2.             Debug.Log(this);
    3.             Debug.Log(this == null);
    I'd get this:
    upload_2023-9-1_0-59-47.png

    I destroyed the object, so it to returns "null" lower-case and says it's null when compared via ==. Despite it most certainly exists because it's "this"... how would this function even run if "this" wasn't a thing?

    It's cause unity puts it in the destroyed state. Which is similar to the fake null object above. But if you access its members it won't say the "UnassignedReferenceException" and instead will say the object is destroyed.

    Why does unity do this? Eh... it's been like it for years. It allows for more helpful exceptions rather than a generic "NullReferenceException". Arguably you should be able to figure out what the problem is by just looking at the line that it happened on... but unity did this extra flavor very early in Unity's life and it just stayed.

    And as for the discrepancy between "null" and "Null" it's because it's 2 different pieces of code running in the background that stringify the message for the console and they were written by 2 different developers at 2 different times and no one bothered to be all that concerned about consistency of what the result of those 2 stringify lines would look like. It doesn't hurt/change anything, but does result in being a tiny artifact that hints at what actually is going on under the hood.
     
    Last edited: Sep 1, 2023
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    Oh I want to come back and note something about this 'UnassignedReferenceException' as well as 'MissingReferenceException'.

    Hey, Unity, why doesn't UnassignedReferenceException inherit from NullReferenceException?

    It's not well documented what I talked about above, especially the serialized field thing. Yet we have 3 different exceptions that could be associated with a "null reference".

    NullReferenceException - thrown if you attempt to access a true null object
    MissingReferenceException - thrown if you attempt to access a destroyed object that == null
    UnassignedReferenceException - thrown if you attempt to access the editor time fake null object that results from an unassigned serialized field

    Note that in C# you can filter your try/catch statements to catch specific exception types by inheritance. If you say:
    Code (csharp):
    1. catch (System.Exception ex) { }
    You will catch all exceptions since all exceptions inherit from System.Exception.

    Where as if you do:
    Code (csharp):
    1. catch (NullReferenceException nex) {}
    You'll catch only if the object was null. And all other exceptions will bubble up.

    Thing is since MissingReferenceException and UnassignedReferenceException don't inherit from NullReferenceException I actually end up having to write something like:
    Code (csharp):
    1. catch (NullReferenceException ex)
    2. {
    3.     Debug.Log("HAD AN NULL REF EXCEPTION!");
    4. }
    5. catch (MissingReferenceException mex)
    6. {
    7.     Debug.Log("HAD AN MISSING REF EXCEPTION!");
    8. }
    9. catch (UnassignedReferenceException uex)
    10. {
    11.     Debug.Log("HAD AN UNNASSIGNED REF EXCEPTION!");
    12. }
    Now... an argument COULD be made that MissingReferenceException shouldn't inherit from NullReferenceException because well, it's actually not null, and you shouldn't actually expect it to be null since it was destroyed. This distinction could be helpful since many have debated the whole "destroyed object == null" thing for as long as Unity has been around.

    BUT

    UnassignedReferenceException ????????

    The object SHOULD be null. Nothing ever got assigned to it. Heck, the way the serializer deals with serialized fields varies from runtime to editor time and so the exception thrown varies based on that alone. I get that there's a level of informational ease that comes out of this fake object so the displayed message is more informative.

    But why not inherit from NullReferenceException?

    Cause here's the thing. You didn't need to shove a fake null object in there to get that information in the debug console. The debug console prints all uncaptured exceptions, and you can get the stack trace and while non-trivial you could suss out which variable it was and get the same information for printing.

    Though of course this means said information wouldn't be available if you attempt to capture the exception in a try/catch as a user/developer (as opposed to internally as unity). But ummm... if I didn't know about this weird fake null object, which due to lack of good documentation on this editor only behavior, means I would like try to catch it via 'NullReferenceException' anyways and being left going "why isn't this getting caught????"

    ...

    I don't know. Just little things I think about sometimes.
     
    SisusCo likes this.
  12. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,834
    I don't really care where the exceptions specialize or inherit.

    If you're designing web commerce apps with massive servers and millions of automated unit tests in your daily CI/CD, and you can stop your microservices upstream, then designing with exceptions makes sense.

    If you're designing a game, it's much better to make the code as streamlined as possible and avoid exceptions as much as possible. Exception handling is very slow, and Unity will likely call the same code path again on the next Update so it's slow again and again and again and again. Doing the null check yourself and doing the least gameplay-damaging action when you find it will be much more performant. During development, include the Debug.Log calls so you can spot the issues and fix the bugs before you ship, but once you ship, you still want the worst case to be performant.
     
  13. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,104
    I guess the pain could be alleviated a little bit with an extension method like this:
    Code (CSharp):
    1. catch(Exception ex)
    2. {              
    3.     if(ex.IsNullReferenceExceptionOrSimilar())
    4.     {
    5.         Debug.Log("HAD A NULL REF–ESQUE EXCEPTION!");
    6.         return;
    7.     }
    8.  
    9.     throw;
    10. }
    Code (CSharp):
    1. public static class ExceptionExtensions
    2. {
    3.     public static bool IsNullReferenceExceptionOrSimilar(this Exception ex)
    4.         => ex is NullReferenceException or MissingReferenceException or UnassignedReferenceException;
    5. }
    But yeah, definitely not an optimal situation...
     
  14. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,104
    Haha, I was thinking to myself that it would be cool if in some future C# version we could be able to use the when keyword with the catch clause...

    Well, it turns out that is actually already possible since C# 6.
    Code (CSharp):
    1. catch(Exception ex) when (ex.IsNullReferenceExceptionOrSimilar())
    2. {          
    3.     Debug.Log("HAD A NULL REF EXCEPTION–ESQUE EXCEPTION!");
    4.     return;
    5. }
    Today I learned.
     
    Last edited: Sep 1, 2023
    lordofduct likes this.
  15. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    Do mind you this is just a mid day rant about a lot of nothing.

    But at the end of the day... my entire point is "why not follow standard C# approach to your C# driven tool used by a mass number of people if not to utilize C# standard practices. Less you want your C# users to show up and go 'hey, this isn't following C# standard practices!' or worse just confuse them because it's obfuscated behind some cryptic behavior that isn't documented."

    Cause I mean sure, you might not use try/catch in your game logic, heck I barely do either. Hence why it's not something I'm tooting out my horn on a daily basis. But we're not the only people using Unity.

    It's just one of those things that's "weird".
     
  16. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,834
    Yeah, I get the appeal of extending in-kind. The ==null overload hack of Unity is bad enough, there's nothing that will make it more "idiomatically C#-ish". And I think it's fair to say that Unity is a big enough framework it has its own idioms, that it doesn't need to be a slave to the wider C# idiom. Like Quebec French vs French.
     
  17. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    I wouldn't call this shagrinning being a slave, or even a choice, but rather just a plane old oversight and not really considering it as an option. There's no good reason to have it as it currently is; aside from "we just didn't bother, and it's not really worth it to change it after the fact".

    Which I mean, is fine.

    Just you know... one of those "huh, that's weird" type things. Like a light switch that turns on upside down.