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

Resolved Why am I seeing partial serialization of private members?

Discussion in 'Scripting' started by AxonGenesis, Jul 9, 2023.

  1. AxonGenesis

    AxonGenesis

    Joined:
    Jun 24, 2016
    Posts:
    81
    I've been encountering a strange problem that goes against my expectation and understanding of script serialization in Unity.

    When I define private member variables in my MonoBehaviour or ScriptableObject classes, unless I explicitly attribute them NonSerializable, Unity is holding onto the data during hot reloading (after recompiling scripts).

    Consider the following example:

    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4.  
    5. [ExecuteInEditMode]
    6. public class SerializationTest : MonoBehaviour
    7. {
    8.     //[NonSerialized]
    9.     private float value = 0f;
    10.  
    11.     private void Awake()
    12.     {
    13.         Debug.Log($"Awake:{value}");
    14.     }
    15.  
    16.     private void OnEnable()
    17.     {
    18.         Debug.Log($"OnEnable:{value}");
    19.         value = UnityEngine.Random.value * 100f;
    20.     }
    21. }
    22.  
    By default, I would expect the private member 'value' not to be serialized and revert to 0, which is true when the scene is reloaded or entering play mode. However, after script recompilation the value persists and does not reset to 0, unless I specifically attribute it with [NonSerialized].

    If you test the code above with NonSerialized commented out, you'll see in the console that OnEnable is called and still has the previously assigned value, whereas if NonSerialized is uncommented the value is 0 (as is expected).

    It seems there is a middle ground where the value is partially serialized (or at least stored by Unity) during script recompilation only. I have also noticed that private members are visible in the Inspector when debug mode is enabled, except if the NonSerialized attribute is applied.

    This behavior has caused some bugginess in my code due to objects holding onto old values, leading to a state of partial initialization. The IDE may suggest [NonSerialized] is unnecessary for private members, however what I am experiencing is contrary to that idea and therefore to ensure my private data is fully reset upon reload I must qualify the members with the NonSerialized attribute.

    I don't recall this being the case in earlier versions of Unity, though it may have been something I overlooked. Here is the related docs which as far as I have found doesn't explain the behavior I am seeing.
    https://docs.unity3d.com/Manual/script-Serialization.html

    Does anyone have any insight into this? For now, my workaround is simply to be very explicit with my attributes in all variable declarations.
     
  2. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,900
    Remove
    ExecuteInEditMode
    . It causes
    OnEnable
    to run and setting the value of the member
    value
    .
    If you don't execute it in the editor after a domain reload, it will fall back to zero.
     
  3. AxonGenesis

    AxonGenesis

    Joined:
    Jun 24, 2016
    Posts:
    81
    Using ExecuteInEditMode is required for most of my scripts since they operate in edit mode for authoring functionality, so that is non-negotiable and also necessary to demonstrate the point I am making.

    OnEnable first outputs to the console the existing 'value' before assigning a new random value. So the first time you'll get console output of "OnEnable:0" (as expected), but then each time thereafter recompiling scripts it outputs "OnEnable:83.32" or whatever random value was assigned previously. It should be 0 every single time if the value is not serialized.

    The underlaying issue is that Unity is holding the private value even though technically it should not be. Using the NonSerialized attribute fixes it. I just want to understand why. I presume that Unity is copying component values using reflection or something during hot reloading, rather than reloading the scene, since OnAwake is not called after script recompile.
     
  4. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,111
    That behaviour, while very surprising and kinda illogical, is actually documented and by design. Quote from the docs:
    Source: https://docs.unity3d.com/Manual/script-Serialization.html

    Though, as you have mentioned, it sadly does not explain WHY it's done like that.
     
    Last edited: Jul 9, 2023
    Bunny83 and AxonGenesis like this.
  5. AxonGenesis

    AxonGenesis

    Joined:
    Jun 24, 2016
    Posts:
    81
    Ah thank you! I completely overlooked that part. At least I know now and it is for sure a documented and intended behavior. It's an odd case, but I assume there is a good reason for it.
     
    _geo__ likes this.