Stored reflected value becomes null for no reason

Hi

I’ve stored the “MemberInfo” reflected value of a variable, and every time I try to set an object value to that variable, it just goes away, AND ONLY FOR THAT ONE SPECIFIC MOMENT. I have a Debug.Log that runs frequently to print out if the property “memberInfo” is null and NEVER does it say that memberInfo is null. Even after the SetValue method has been called, it’s still not null. I have absolutely no idea what is going on and it is driving me up the wall! NOWHERE does it say to set “memberInfo” to null.

Here’s some of my code:

The actual SetValue method.

public void SetValue(T value)
{
    FieldInfo field = (FieldInfo)memberInfo;
    field.SetValue(target, value);

    m_currentValue = value;
}

The event that happens whenever I want to set the value.

void OnSetValue(UnityObject o)
{
    SetValue(o);
}

Also, note that this is at runtime.

As I said, 0 clues what’s happening, and it’s driving me to the brink of insanity! :rage:

EDIT: Here’s a big wall of code with what I believe is important for this problem. It’s not 100% everything, but the other parts shouldn’t matter.

Code

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using UnityObject = UnityEngine.Object;

public class PropertyEditor : MonoBehaviour
{
    [SerializeField]
    protected Text m_label;
    private bool m_lockValue;
    protected bool lockValue { get { return m_lockValue; } }
    protected object target { get; private set; }
    private MemberInfo mi;
    public MemberInfo memberInfo { get { return mi; } set { if (value == null) Debug.LogError("IT'S NULL!"); mi = value; } }
    protected Type memberInfoType { get; set; }
   
    private ALE_UI m_ui;
    protected ALE_UI UI { get { if (m_ui == null) m_ui = FindObjectOfType<ALE_UI>();return m_ui; } }

    private void Awake()
    {
        AwakeOverride();
    }

    private void Start()
    {
        StartOverride();
    }

    private void OnDestroy()
    {
        OnDestroyOverride();
    }

    protected virtual void AwakeOverride() { }
    protected virtual void StartOverride() { }
    protected virtual void OnDestroyOverride() { }

    public void Init(object target, MemberInfo memberInfo, Type memberType, string label = null)
    {
        if(m_label) m_label.text = label;
        InitOverride(target, memberInfo, memberType, label);
    }

    protected virtual void InitOverride(object target, MemberInfo memberInfo, Type memberType, string label = null)
    {
        if (target == null)
            throw new ArgumentNullException("target");

        this.target = target;
        this.memberInfo = memberInfo;
        memberInfoType = memberType;
    }
}

public abstract class PropertyEditor<T> : PropertyEditor
{
    protected T m_currentValue;

    public void SetValue(T value)
    {
        if(memberInfo is PropertyInfo)
        {
            PropertyInfo prop = (PropertyInfo)memberInfo;
            prop.SetValue(target, value, null);
        }
        else
        {
            FieldInfo field = (FieldInfo)memberInfo;
            field.SetValue(target, value);
        }

        m_currentValue = value;
    }
}

public class ObjectEditor : PropertyEditor<UnityObject>
{
    [SerializeField]
    private Button m_button;
    [SerializeField]
    private Text m_text;

    protected override void AwakeOverride()
    {
        base.AwakeOverride();
        m_button.onClick.AddListener(OnButtonClick);
        UI.selectObjectWindow.onSelectItem.AddListener(OnSetValue);
    }

    void OnSetValue(UnityObject o)
    {
        SetValue(o);
    }

    protected override void OnDestroyOverride()
    {
        base.OnDestroyOverride();
        UI.selectObjectWindow.onSelectItem.RemoveListener(SetValue);
    }

    private void OnButtonClick()
    {
        UI.selectObjectWindow.Show(memberInfoType, true, new Color(0, 0, 0, 0.5f));
    }
}

public class ALE_SelectObjectWindow : ALE_Window<ALE_SelectObjectWindow>
{
    [SerializeField]
    private int m_limitBeforeSlowdown = 15;
    [SerializeField]
    private Button m_objectButton;
    [SerializeField]
    private Text m_header;
    [SerializeField]
    private InputField m_searchField;
    [SerializeField]
    private Button m_selectButton;
    [SerializeField]
    private Button m_cancelButton;

    private int m_selectedItem;
    private List<Button> m_buttons = new List<Button>();
    private Type m_currentType;
    private List<ALE_Object> m_objects = new List<ALE_Object>();
    private List<UnityObject> m_unityObjects = new List<UnityObject>();

    public class OnSelectItem : UnityEvent<UnityObject> { }
    private OnSelectItem m_onSelectItem = new OnSelectItem();
    public OnSelectItem onSelectItem { get { return m_onSelectItem; } set { m_onSelectItem = value; } }

    private void Start()
    {
        m_searchField.onValueChanged.AddListener(OnSearch);
        m_cancelButton.onClick.AddListener(Dismiss);
        m_selectButton.onClick.AddListener(OnSelect);
    }

    private void OnSearch(string value)
    {
        if (!string.IsNullOrEmpty(value))
        {
            for (int i = 0; i < m_objects.Count; i++)
            {
                if (!m_objects[i].name.ToLower().Contains(value.ToLower()))
                {
                    m_buttons[i].gameObject.SetActive(false);
                }
                else
                {
                    m_buttons[i].gameObject.SetActive(true);
                }
            }
        }
        else
        {
            m_buttons.ForEach(b => b.gameObject.SetActive(true));
        }
    }

    private void OnSelect()
    {
        Dismiss();
        onSelectItem.Invoke(m_selectedItem == -1 ? null : m_unityObjects[m_selectedItem]);
    }

    public void Show(Type type)
    {
        Show();
        Setup(type);
    }

    public void Show(Type type, bool dismissOnBackgroundClick)
    {
        Show(dismissOnBackgroundClick);
        Setup(type);
    }

    public void Show(Type type, bool dismissOnBackgroundClick, Color backgroundColor)
    {
        Show(dismissOnBackgroundClick, backgroundColor);
        Setup(type);
    }

    void Setup(Type type)
    {
        m_selectedItem = -1;
        m_unityObjects.Clear();
        m_searchField.text = "";
        m_currentType = type;
        m_header.text = "Select " + type.Name;

        m_objectButton.gameObject.SetActive(false);

        for (int i = 0; i < m_buttons.Count; i++)
        {
            Destroy(m_buttons[i].gameObject);
        }

        m_buttons.Clear();

        Component[] rawObjects = FindObjectsOfType(m_currentType) as Component[];
        m_objects = new List<ALE_Object>();

        //m_unityObjects.ForEach(o => Debug.Log(o.name));

        for (int i = 0; i < rawObjects.Length; i++)
        {
            GameObject go = rawObjects[i].gameObject;
            ALE_Object aleObj = go.GetComponent<ALE_Object>();

            if (aleObj != null)
            {
                m_objects.Add(aleObj);
                m_unityObjects.Add(go);
            }
               
        }

        if (m_objects.Count > m_limitBeforeSlowdown)
        {
            StartCoroutine(SlowButtonCreation(m_objects.ToArray()));
        }
        else
        {
            for (int i = 0; i < m_objects.Count; i++)
            {
                int index = i;
                CreateButton(index, m_objects[i].name);
            }
        }
    }

    IEnumerator SlowButtonCreation(ALE_Object[] objects)
    {
        for (int i = 0; i < objects.Length; i++)
        {
            int index = i;
            CreateButton(index, objects[i].name);
            yield return new WaitForEndOfFrame();
        }
    }

    void OnClick(int index)
    {
        if (m_selectedItem != -1 && m_buttons[m_selectedItem])
            m_buttons[m_selectedItem].interactable = true;

        m_selectedItem = index;
        m_buttons[m_selectedItem].interactable = false;
    }

    void CreateButton(int index, string name)
    {
        Button newButton = Instantiate(m_objectButton, m_objectButton.transform.parent);
        newButton.GetComponentInChildren<Text>().text = name;
        newButton.onClick.AddListener(delegate { OnClick(index); });
        newButton.gameObject.SetActive(true);
        m_buttons.Add(newButton);
    }
}

How do you set the memberInfo? Does any other class have access to the variable and might be changing its value?

Also, is the “FieldInfo field = (FieldInfo)memberInfo” the line that gives a NullPointerException?

“memberInfo” is set in an Init method called a bit earlier. At that point, it’s not null. And no, it is not the line you mentioned that gives a null exception. I’m not sure why I didn’t include the Debug.Log but it’s actually null before that is called.

Could you post the complete code or a minimal test case that reproduces the problem. It’s kinda helpless just to guess what’s going on.

    FieldInfo field = (FieldInfo)memberInfo;
    field.SetValue(target, value);

Are you 100% certain the MemberInfo is a field?

Why is the memberInfo stored typed that, if you’re going to be casting it to ‘FieldInfo’ when you use it?

Usually when I see coerced casting like that, it’s a red flag something might be wrong in the design.

If you have a field in a class typed as something other than how you use it in that class is weird. Casting should be when you receive an object from elsewhere as a parent type, and you need to cast down to the specific you know it should be… and even then you should be testing to confirm that it IS that type.

So… the code where you get the MemberInfo, what member is it reflecting, and are you 100% certain it’s a ‘field’ and not say a ‘Property’ or a ‘Method’?

I do have checks in order to check for that, I just didn’t include it here to make the page a bit smaller. But I know for a fact that this part is not a problem. The problem here is that memberInfo is set to null somewhere, which is nowhere.

I have done some testing and it turns out when the “OnSetValue” method is called by an event, memberInfo is set to null for no reason. If I call OnSetValue in any other way, it works just fine.

I don’t think anyone’s going to be able to help you unless you post more code. Given that computers are part of this physical universe operating under rational laws, it seems unlikely that anything is “somewhere, which is nowhere”. Is your code summoning an Elder God from a Universe That Should Not Be? That might cause dimensional instability. Otherwise, it’s probably a mistake in your code.

1 Like

I am trying to collect what parts would be necessary for an example, I’m just having a hard time deciding what’s important and what’s not.

Since you don’t know what’s causing the problem, it’s going to be hard to decide what’s important in it.

So either show us it all (in context).

OR

Recreate the bug by pulling out what you think is important, putting it into its own contained scenario (like a unit test), and recreate it on this smaller scale that you’re trying to construct. Confirming the bug exists in this controlled scenario before posting.

Otherwise, what you end up posting most likely won’t contain the problem.

In other words, computers bug because we instruct them to do so.

I have added a big wall of a code to the original post that might be able to help.

    private MemberInfo mi;
    public MemberInfo memberInfo
    {
        get { return mi; }
            set
            {
                if (value == null)
                    Debug.LogError("IT'S NULL!"); // Set a break point here...
                mi = value;
           }
        }

… and track back in the call stack why value is null.

That condition is never met. There is NOTHING setting it to null.

Also, can we get the exact exception message you get, with the full stack included in the error?

On an unrelated issue, I noticed this line of code:

    protected override void AwakeOverride()
    {
        base.AwakeOverride();
        m_button.onClick.AddListener(OnButtonClick);
        UI.selectObjectWindow.onSelectItem.AddListener(OnSetValue);
    }

    void OnSetValue(UnityObject o)
    {
        SetValue(o);
    }

    protected override void OnDestroyOverride()
    {
        base.OnDestroyOverride();
        UI.selectObjectWindow.onSelectItem.RemoveListener(SetValue);
    }

You AddListener of OnSetValue, bur RemoveListener of SetValue.

This isn’t the cause of your issue, just noticed it off hand.

Thanks for pointing that little miss. Fixed it.

And is it this you’re looking for?
Error

NullReferenceException: Object reference not set to an instance of an object
Gameotiv.ALE.UI.Editors.PropertyEditor1[T].SetValue (.T value) (at Assets/Gameotiv/ALE/Scripts/UI/Editors/PropertyEditor.cs:219) Gameotiv.ALE.UI.Editors.PropertyEditors.ObjectEditor.OnSetValue (UnityEngine.Object o) (at Assets/Gameotiv/ALE/Scripts/UI/Editors/Property Editors/ObjectEditor.cs:44) UnityEngine.Events.InvokableCall1[UnityEngine.Object].Invoke (System.Object[ ] args) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:189)
UnityEngine.Events.InvokableCallList.Invoke (System.Object[ ] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:635)
UnityEngine.Events.UnityEventBase.Invoke (System.Object[ ] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:771)
UnityEngine.Events.UnityEvent1[T0].Invoke (.T0 arg0) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent_1.cs:53) Gameotiv.ALE.UI.Editors.Windows.ALE_SelectObjectWindow.OnSelect () (at Assets/Gameotiv/ALE/Scripts/UI/Editors/Windows/ALE_SelectObjectWindow.cs:68) UnityEngine.Events.InvokableCall.Invoke (System.Object[ ] args) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:154) UnityEngine.Events.InvokableCallList.Invoke (System.Object[ ] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:635) UnityEngine.Events.UnityEventBase.Invoke (System.Object[ ] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:771) UnityEngine.Events.UnityEvent.Invoke () (at C:/buildslave/unity/build/Runtime/Export/UnityEvent_0.cs:53) UnityEngine.UI.Button.Press () (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:35) UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:44) UnityEngine.EventSystems.ExecuteEvents.Execute (IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:50) UnityEngine.EventSystems.ExecuteEvents.Execute[IPointerClickHandler] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.EventFunction1 functor) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:261)
UnityEngine.EventSystems.EventSystem:Update()

Again, this reaffirms to me that I think your memberInfo is not a field or property.

You have this code to confirm the memberinfo type:

        if(memberInfo is PropertyInfo)
        {
            PropertyInfo prop = (PropertyInfo)memberInfo;
            prop.SetValue(target, value, null);
        }
        else
        {
            FieldInfo field = (FieldInfo)memberInfo;
            field.SetValue(target, value);
        }

Thing is, MemberInfo can be more than JUST property or field.

This code assumes if it’s not a Property, than it is a Field, not true.

Could also be an event (EventInfo) or a method (MethodBase).

1 Like
public void SetValue(T value)
{
    FieldInfo field = (FieldInfo)memberInfo;
    if( field == null )
         Debug.Log("is null"); // Break point here...

    field.SetValue(target, value);
    m_currentValue = value;
}

… and check memberInfo.

Well if it isn’t, then nothing happens. The problem here is that memberInfo is null. I just added a little Debug.Log(memberInfo) to that method and it just says “Null”.

Of course field is going to be null since memberInfo is null.

Yes, that is.

Question, when and where does ‘Init’ get called?

None of your code you’ve shared has this. Are you certain it was initialized at the point the event is raised? And what MemberInfo are you passing in?

Not true, your code as it stands assumes that if it’s not a PropertyInfo, that it’s a FieldInfo. That’s not doing nothing.

But yes, if memberInfo is null, so would fieldInfo.

In which case, maybe Init hasn’t been called yet.