Search Unity

UnassignedReferenceException and Null-Check

Discussion in 'Scripting' started by maltakereuz, Feb 27, 2018.

  1. maltakereuz

    maltakereuz

    Joined:
    Mar 29, 2015
    Posts:
    53
    I often check in Start()-method, if all needed references are set in inspector. So i wrote a utility method, for this like this:

    Code (CSharp):
    1.  
    2. public static void WarnIfNull(object o, string name) {
    3.     if (o == null) {
    4.         Debug.LogWarning(name + " is NULL");
    5. ...
    6.  
    so i can use it in component init code like this:
    Code (CSharp):
    1.  
    2. public class MyComponent : MonoBehavior {
    3.     public AudioClip sound;
    4.     void Start() {
    5.         MyLogUtils.WarnIfNull(sound, "sound");
    6. ...
    7.  
    Nothing special, interesting so far... But it does not work. Assuming i forgot to assign reference in inspector. If i null-check inside of MyComponent.Start(), null check works correct. But if doing exact same null-check inside of MyLogUtils.WarnIfNull(), null check is FALSE. I can not explain this, it is really weird.

    So i tryied to debug it, if object is not null, then what is it? (it was never assigned). I can cast it to AudioClip like this

    Code (CSharp):
    1.  
    2. if (o is AudioClip) {
    3.     AudioClip a = (AudioClip) o; // this works
    4. }
    5.  
    but if i call any method/property from this object like this:

    Code (CSharp):
    1.  
    2. var a_name = a.name;
    3. a_name.ToString();
    4.  
    i am getting UnassignedReferenceException.

    I know, it sounds strange, that null-check of same object in call-stack behaves not same. But is looks like this. I do not really understand what is the reason for that. Docs for UnassignedReferenceException somehow do not explain it.

    So my missing-reference method looks like this now:

    Code (CSharp):
    1.  
    2. public static void WarnIfNull(object o, string name) {
    3.     bool unassigned = false;
    4.     if (o is AudioClip) {
    5.         try {
    6.             AudioClip a = (AudioClip) o;
    7.             var a_name = a.name;
    8.             a_name.ToString();
    9.         }
    10.         catch (UnassignedReferenceException e) {
    11.             unassigned = true;
    12.         }
    13.     } if (o == null || unassigned) {
    14.         Warn(name + " is NULL (unassigned in inspector, but somehow can be not null)");
    15.     }
    16. }
    17.  
    Any suggestions explanations are welcome. So, my questions:
    1) Why the same reference is null in one methode and not null in another. I just pass it by reference but, null-check result ist different. Is this some special UnityEditor stuff i should know?
    2) Is this whole way to warn about forgotten references OK?
     
  2. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    You're probably running into Unity's fake null system. The reference itself may still be valid but still treated as "null" by Unity if the underlying object has been Destroy()'ed.

    The object type is outside of Unity's scope. There, you will see the reference is actually not null, but it still won't be safe to try to reference any of its members (as you've discovered).

    Try using the UnityEngine.Object type instead for your "null" checking.
     
    JoeStrout likes this.
  3. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    @BlackPete has the right of it (and ninja'd my own reply to the same effect).

    Also, for what it's worth, I do such checks simply like this:

    Code (CSharp):
    1. Debug.Assert(someRef != null);
    2.  
    ...right in the Start or Awake method (or just after calling GetComponent or GameObject.Find or whatever). No need for a helper method; I think this is clear enough, and if the assertion is thrown, just double-click it in the Console to jump right to that line.
     
    BlackPete likes this.
  4. maltakereuz

    maltakereuz

    Joined:
    Mar 29, 2015
    Posts:
    53
    @BlackPete @JoeStrout oh, thank you! very interesting

    so it's actually not pure c#, it's not overload for some special class, but change of compiler behavior
     
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    It doesn't change the compiler behaviour either. It's a really, really ugly hack for UnityEngine.Object. == is overridden to consider objects that doesn't exist in the c++ engine to be null-like, and return true if you == null them. This intersects with a different, dumb hack, where Unity inserts bogus objects in unassigned fields in MonoBehaviours in the editor only. These bogus objects have some data attached to them so that instead of a NullReferenceException you get an UnassignedReferenceException with extra information. It causes all kinds of problems, and I don't think it solves anything.
    Since == is an operator, it's a static method, so which == to use is decided at compile-time, unlike virtual methods that are looked up at runtime.

    To get your method to do what you want, just type obj as UnityEngine.Object rather than System.Object (object). Or go with @JoeStrout's method.
     
    JoeStrout likes this.
  6. Gandamir

    Gandamir

    Joined:
    Nov 22, 2017
    Posts:
    36
    I think you are running into scoping issues based upon this. As soon as you leave the first if statement, AudioClip a goes out of scope. Try something like this instead:

    Code (CSharp):
    1.  
    2. class MyClass
    3. {
    4.     private AudioClip audioClip;
    5.     void Awake()
    6.     {
    7.         if (o is AudioClip)
    8.             audioClip = (AudioClip) o; // this works
    9.     }
    10.  
    11.     void SomeMethod()
    12.     {
    13.         var a_name = a.name;
    14.         a_name.ToString();
    15.     }
    16.  
     
  7. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    @Gandamir, you missed the point of the AudioClip thing — the OP was using it as a goofy way (pardon me for saying so) of testing whether an object was null. The only benefit of it being AudioClip is that AudioClip happens to derive from UnityEngine.Object; any other Unity class would have worked just as well, and he had no intention of actually using this reference outside his null-check method.
     
  8. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    While on the subject of operator overloading, it's also worth noting that Unity also added an implicit bool cast to check for nulls. So you can get away with using stuff like this on Unity objects:

    Code (csharp):
    1.  
    2. if (!someUnityObj) // won't work with non-Unity types!
    3.    return;
    4.  
    This kind of thing can trip you up when reading unfamiliar code that uses both Unity and non-Unity objects. At least it won't compile if you accidentally tried to use this on non-Unity objects.

    This is just to reinforce the point that Unity does not change the compiler behaviour, it's simply operator overloading magic (which I personally find annoying).
     
  9. maltakereuz

    maltakereuz

    Joined:
    Mar 29, 2015
    Posts:
    53
    Thank you. So if (!obj) would even work. Now i understand.
    Thank you very much. This part is not obvious at all.
     
  10. This is a problem because I think the solution you wrote is lazy and not maintainable.
    Code (CSharp):
    1. public static void WarnIfNull(object o, string name) {
    2.     bool unassigned = false;
    3.     if (o is AudioClip) {
    4. [...]
    Instead of this, do this:
    Code (CSharp):
    1. public static void WarnIfNull(AudioClip o, string name) {
    2.     bool unassigned = false;
    3. [...]
    And create an overload method for every type you want to support. This type of if/elseif/[...] is a very ugly source of bugs.
     
  11. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    ...And, now @LurkingNinjaDev has also missed the point of the AudioClip hack. :)
     
  12. Oh, that is so true. :D Okay, I have an excuse, I'm just starting my first coffee in the morning, I can't read properly before that! :D (I know, I shouldn't comment either... eh)
     
    JoeStrout likes this.