Unintended function is added to the button as an Addlistener

Hello, everyone!
This has been happening since last night and I haven’t solved it yet. I’ve searched and experimented with creating a local variable inside the loop, or even creating two coroutine functions that create different buttons, but neither worked.

public void InitSlotless(GameObject elementPreFab, List<Sprite> spriteList, List<PCBase> fromDB)
    {
        ClearGrid();
        StartCoroutine(MakeChild(elementPreFab, spriteList, fromDB));
    }

    public void InitSlot(GameObject elementPreFab, List<Sprite> spriteList,
                        List<PCBase> fromDB, List<KeyValuePair<bool, int>> clothOrRigSlot)
    {
        ClearGrid();
        StartCoroutine(MakeChildren(elementPreFab, spriteList, fromDB, clothOrRigSlot));
    }

IEnumerator MakeChildren(GameObject elementPreFab, List<Sprite> spriteList,
                        List<PCBase> fromDB)
    {
        for (int i = 0; i < spriteList.Count; ++i)
        {
            GameObject go = Instantiate(elementPreFab, _scrollView.content.transform, false);
            go.name = $"{i}";
            int index = i;

            go.GetComponent<ItemChildData>().Initialize(index, fromDB[i]);
            SelectOrDeselectSlotlessItem(go);

            ObjectTypeButton btn = go.GetComponentInChildren<ObjectTypeButton>();
            btn.GetComponentInChildren<ObjectTypeButton>().PressEndEvent.AddListener(() =>
                ClickItem(this, index));
            yield return null;
        }
    }
IEnumerator MakeChildren(GameObject elementPreFab, List<Sprite> spriteList,
                        List<PCBase> fromDB, List<KeyValuePair<bool, int>> clothOrRigSlot)
    {
        for (int i = 0; i < spriteList.Count; ++i)
        {
            GameObject go = Instantiate(elementPreFab, _scrollView.content.transform, false);
            go.name = $"{i}";
            int index = i;

            go.GetComponent<ItemChildData>().Initialize(index, fromDB[i], clothOrRigSlot[i]);
            SelectOrDeselectItem(go);
            ObjectTypeButton btn = go.GetComponentInChildren<ObjectTypeButton>();
            btn.GetComponentInChildren<ObjectTypeButton>().PressEndEvent.AddListener(() =>
                ClickItem(index));
            yield return null;
        }
    }

public void ClickItem(GridViwer gird, int index)
    {
        Debug.Log($"{gird.name}, {index}@@@");      
        gridItemClickEvent.Invoke(gird, index);
    }
public void ClickItem(int index)
    {
        Debug.Log($"{this.name}, {index}!!!");    
        gridItemClickEvent.Invoke(this, index);
    }

After writing the code above (I’m experimenting, so there are some inefficiencies), I pressed the btn that I thought was connected to ‘ClickItem(index)’ and looked at the output log. The log shows that it was taken from ‘ClickItem(GridViewer gird, int index)’. Also, the index value is different from the actual order of the btn.

I don’t understand why a different function is connected to the btn than the function ‘ClickItem(index)’ in the code. I also don’t understand why the order value I get from the local variable ‘int index = i’ doesn’t work either…
If you have any solution or clue about this phenomenon, I would really appreciate it if you could share it.

I haven’t figured out why I’m getting the results above, but I’ve solved the problem.
It’s messy, but… I used the GET function to directly get the values I needed to substitute and substituted them in, as shown below, and it worked.
I’m leaving it here in case anyone else runs into a similar situation.

int j = i;
            //GridViewer temp = this;
            ObjectTypeButton btn = go.GetComponentInChildren<ObjectTypeButton>();
            btn.GetComponentInChildren<ObjectTypeButton>().PressEndEvent.AddListener(() =>
                ClickItem(this == _UIManager.GetMGirdViewer()
                    ? _UIManager.GetMGirdViewer()
                    : _UIManager.GetIGirdViewer(), j));

I think it would make more sense to maintain a list of all the buttons you have instantiated, then when one is clicked, you can look up it’s index and pass that along in the delegate.

Or let the component on these buttons express a normal C# delegate, rather than a Unity event. It maintains its own index, and this component can hook into these delgates, and bubble up the callbacks.

The issue you were running into I believe is due to how anonymous methods capture variables by reference, even if they’re a value type.

1 Like