- Home /
What is the difference between storing value of loop counter and then using it, versus directly using the loop counter value.
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:
https://answers.unity.com/questions/875588/unity-ui-dynamic-buttons.html
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?
Answer by exploringunity · Jul 12, 2020 at 07:23 PM
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!
Answer by IggyZuk · Jul 12, 2020 at 07:02 PM
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.
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Is it possible to check which hand is grabbing and the controllers are grabbing the same object. 0 Answers
How to examine an object/examination system? 1 Answer
Lerping & OnCollisionEnter - how do i lerp without update?? 1 Answer
How can I make Stream Writer overwrite a txt file? 0 Answers