Hi, I was trying to create a script to generate buttons dynamically. I sucessfully managed to do that by following the answers to this question:
The code I got from here was something like this:
Button tempButton = SelectWeaponButton.GetComponent<Button>();
tempButton.onClick.AddListener(() => ShowWeapons());
for(int i = 0; i < AvailableMissiles.Length; i++)
{
GameObject b = Instantiate(WeaponButtonPrefab);
b.transform.SetParent(WeaponButtonParent.GetComponent<RectTransform>());
b.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -(UIOffsetY*i+UIOffsetY));
Button btn = b.GetComponent<Button>();
int tempInt = i;
btn.onClick.AddListener(()=>SetCurrentWeapon(tempInt));
}
This works perfectly well. SetCurrentWeapon function for now only prints “tempInt” to debug.log. In this case, button created when i was 0, shows 0. Button created when i was 1, shows 1.
But then I tried to eliminate the additional step of storing “i” in “tempint”, by directly using i in the next line. So code became something like this:
Button tempButton = SelectWeaponButton.GetComponent<Button>();
tempButton.onClick.AddListener(() => ShowWeapons());
for(int i = 0; i < AvailableMissiles.Length; i++)
{
GameObject b = Instantiate(WeaponButtonPrefab);
b.transform.SetParent(WeaponButtonParent.GetComponent<RectTransform>());
b.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -(UIOffsetY*i+UIOffsetY));
Button btn = b.GetComponent<Button>();
btn.onClick.AddListener(()=>SetCurrentWeapon(i));
}
But now all all button pass “2” to SetCurrentWeapon function (there are 2 buttons, when 3 buttons are there, they will all pass 3).
This is something I found weird. ‘i’ and ‘tempInt’ hold the same value, don’t they? so why does the code work correctly when we run it by storing the value of i in tempInt first, but all show same value (which is equal to total number of buttons) when we try to use i directly?
Welcome to the world of Closures!
Essentially, lamba functions capture the scope of the variables that are being used inside them, in your case it’s the scope of where the variable i exists.
When you call the lambda function it looks into the scope of i and uses its latest value, at which point it’s already been changed.
Using a temp variable is the correct way to do this. It still uses closures but the value is not modified.
Great video on the topic.
Hey @saadjumani,
This is a variable capture issue stemming from the use of closures (lambdas).
This issue is not specific to Unity, and can be demonstrated in a simple C# console application, like so:
using System;
using System.Collections.Generic;
namespace VariableCaptureDemo
{
class Program
{
static void Main(string[] args)
{
var jobQueue = new List<Action>();
for (var i = 0; i < 5; i++)
{
jobQueue.Add(() => Console.WriteLine(i));
}
foreach (var job in jobQueue) { job(); }
}
}
}
This will print out “5 5 5 5 5”, not “1 2 3 4 5” like you might expect.
If we add a variable and pass that to the lambda instead of the loop counter…
for (var i = 0; i < 5; i++)
{
var copy_of_i = i;
jobQueue.Add(() => Console.WriteLine(copy_of_i));
}
… then “1 2 3 4 5” is printed!
Why the difference? Closures close over variables, not over values. There is only 1 i, but there are 5 different copy_of_i variables, if you think about it in terms of pointers and memory allocation.
Since other people smarter than me have already written about it, I’ll just point you to them 
Eric Lippert’s posts on this exact issue – post #1 here and post #2 here
How to capture a variable in C# and not to shoot yourself in the foot and Lambda Expressions, Captured Variables, and For Loops: A Dangerous Combination are also good reads.
Finally, see the official docs on value/reference types, and boxing/unboxing and the official docs on lambda variable capture.
Hope this helps!