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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Button.onClick.AddListener() giving unexpected results

Discussion in 'Scripting' started by Antistone, Mar 20, 2015.

  1. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    I'm creating a list of buttons at runtime and trying to add listeners to each of them. My code goes something like this:

    Code (CSharp):
    1.         for (int i = 0; i < moveHistory.Count; ++i)
    2.         {
    3.             GameObject m = (GameObject)Instantiate(movePrefab);
    4.             m.transform.SetParent(transform, false);
    5.             MoveHistoryItem item = m.GetComponent<MoveHistoryItem>();
    6.             item.mainButton.onClick.AddListener(() => GoToMove(i));
    7.         }
    So I want the first button to call GoToMove(0) when clicked, the second to call GoToMove(1), etc.

    In testing, all of the buttons call GoToMove with the same argument (which I believe is the final value of i when the loop ends).

    Why does this happen, and how can I get my intended behavior?
     
  2. edwin_s

    edwin_s

    Joined:
    Mar 10, 2015
    Posts:
    19
  3. ensiferum888

    ensiferum888

    Joined:
    May 11, 2013
    Posts:
    317
    This is normal, addlistener will capture the instance of i rather than the value of i, to fix just assign it to a local variable:


    Code (CSharp):
    1.    for (int i = 0; i < moveHistory.Count; ++i)
    2.         {
    3.             int local_i = i;
    4.             GameObject m = (GameObject)Instantiate(movePrefab);
    5.             m.transform.SetParent(transform, false);
    6.             MoveHistoryItem item = m.GetComponent<MoveHistoryItem>();
    7.             item.mainButton.onClick.AddListener(() => GoToMove(local_i));
    8.         }
     
  4. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    Your proposed fix appears to work, but I'm having some trouble with your explanation. I am not passing i to AddListener; I am passing a lambda expression. Is that how lambdas work in general? (I've used lambda expressions in many other places and would be surprised not to have revealed my misunderstanding before now if that's the case, but I haven't specifically been looking for it, so I suppose it's possible.) If not, how is the fact that I'm passing the lambda to AddListener() changing the behavior?
     
  5. ensiferum888

    ensiferum888

    Joined:
    May 11, 2013
    Posts:
    317
    Yes sorry it's the lamda expression that does that. I remember seeing this on the MSDN documentation so I think this is the intended behavior.
     
  6. sstadelman

    sstadelman

    Joined:
    Dec 17, 2013
    Posts:
    12
    +10 for solution