6

I currently have two scripts set up in Unity to handle some UI Audio. One is a manager and the other is there to play a sound for a specific UI element. A simplified version of what I have is this:

public class AudioUIManager : MonoBehaviour //Only one of these in the scene
{
    public AudioClip genericUISound; //This is set in the inspector.
}

public class AudioUITextAnimation : MonoBehaviour
{
    [SerializeField]
    private AudioClip specifiedUISound; //This is not set in the inspector
    [SerializeField]
    private AudioUIManager audioUIManager; // I get a reference to this elsewhere

    void Start()
    {
        //Use generic sounds from audio manager if nothing is specified.
        specifiedUISound = specifiedUISound ?? audioUIManager.genericUISound;
        print(specifiedUISound);
    }
}

What I'm trying to achieve here is to have the specifiedUISound field use the sound that it is assigned to it in the inspector. If no sound is assigned then use the generic sound from the UI manager. This saves me assigning the same sound to millions of buttons that require the same sound but gives me the option of having a specific sound for one button if I want to.

However, the null-coalessing operator is asigning null to specifiedUISound even though it is null and the audioUIManager's sound is not. Also, I can get this to work if I use the ternary operator to check for null like so:

specifiedUISound = specifiedUIsound == null ? audioUIManager.genericUISound : specifiedUISound;

Am I misunderstanding the null coalescing operator? Why does this happen?

Edit: Jerry Switalski has pointed out that this only happens when the specifiedUISound is serialized. (if it is public or if it has the [SerializeField] attribute). Can anyone shed some light on what is going on here?

10
  • s = s ?? "test"; works fine here Commented May 31, 2016 at 11:29
  • 2
    So you're saying that foo = foo ?? bar doesn't work, but that foo = foo == null ? bar : foo does? Unity didn't change C# rules, so it must be your code. Did you perhaps go too far in creating a minimal reproducible example so that you removed too much relevant code? What type is AudioClip, is it a struct or a class? Commented May 31, 2016 at 11:29
  • Yeah, I tested this example and it recreates the problem. If you make these two classes and add them to two different game objects in unity then it re-creates it! It works normally if I don't use it in this way though obviously. Commented May 31, 2016 at 11:32
  • I believe that audioUIManager.genericUISound is null while the code is being executed and only becomes non-null later on. Have you debugged this scenario and actually seen the behaviour live? Please output the value of audioUIManager.genericUISound before the code is executed. Commented May 31, 2016 at 11:36
  • @NineBerry Just checked and audioUIManager.genericUISound isn't null before hand. Also, the method with the ternary operator wouldn't work either if it was null. Commented May 31, 2016 at 11:40

2 Answers 2

11

You're running into Unity's custom equality operator:

When a MonoBehaviour has fields, in the editor only, we do not set those fields to “real null”, but to a “fake null” object. Our custom == operator is able to check if something is one of these fake null objects, and behaves accordingly. While this is an exotic setup, it allows us to store information in the fake null object that gives you more contextual information when you invoke a method on it, or when you ask the object for a property. Without this trick, you would only get a NullReferenceException, a stack trace, but you would have no idea which GameObject had the MonoBehaviour that had the field that was null.

While running in the editor, Unity replaces your serialized null with a sentinel value that isn't actually null. This allows them to provide more informative error messages in some circumstances.

Is specifiedUISound equal to null? That depends on how you ask. C# has multiple notions of "equality", including data equality and reference equality.

Some checks will say the values are equal: == and Object.Equals

Others will say they are not equal: ?? and Object.ReferenceEquals

This behavior will only occur in the editor. When running in a standalone build, any null values will just be null.

Sign up to request clarification or add additional context in comments.

Comments

3

It is not really complete answer to your question and very intresting example, but I ran few tests, and it looks like the problem is in SerializeField attribute.

When I run this (someObj is assigned from inspector nullObj is left empty):

    public AudioClip someObj;

    [SerializeField]
    private AudioClip nullObj;

    void Start () 
    {
        Debug.Log("nullObj == null : " + (nullObj == null));
        Debug.Log("someObj == null : " + (someObj == null));

        nullObj = nullObj ?? someObj;

        Debug.Log ("nullObj == null : " + (nullObj == null));
    }

I have this prints:

enter image description here

However getting rid of SerializeField attribute, makes things work as intended:

    public AudioClip someObj;

    private AudioClip nullObj;

    void Start () 
    {
        Debug.Log("nullObj == null : " + (nullObj == null));
        Debug.Log("someObj == null : " + (someObj == null));

        nullObj = nullObj ?? someObj;

        Debug.Log ("nullObj == null : " + (nullObj == null));
    }

gives:

enter image description here

So reasuming:

I don't really know the root of the problem, but what is fact is that Unity3D serializing fields breaks null coalescing operator in Mono engine. I still don't know how, but maybe just due to changing == operator of seriazlized type??

Anyway I hope it helps at least a little bit.

5 Comments

Very interesting, nice find!
I hope it helps, actually you can also reproduce it just by leaving the nullObj as public (Unity will serilize this filed automaticly) - then even if is null, given operator will not work correctly.
Awesome, super helpful. I'll wait and see if anyone knows why this happens and accept this if not!
@gjttt1 This answer is not true. I am still envestigating and will let you know
He's right about public and serialized stuff but it does happen only in the editor. It should work fine when you build it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.