3

I have a situation where I have someFunction(int), and I need to generate programmatically n buttons that will call it. What this means is that I want to create buttons B1, B2, ... Bn that call someFunction(1), someFunction(2), ... someFunction(n) when clicked.

This is how I attempted to do this (semi-pseudocode):

for (int i = 1; i <= n; i++) {
  Button b = new Button();
  b.Caption = "Value " + n; // non-WPF: b.Text = "Value " + n;
  b.Click += (sender, event) => {
    someFunction(i);
  }
}

What bugs me about this is that when I click on the first button (B1), with a debugger over someFunction(i), it tells me that it's calling someFunction(n + 1).

I'm not sure why this is, or how to fix it. The work-around I use is to use, instead of someFunction(i), someFunction(int.Parse(i.ToString()) (to create a copy of i). But this seems shady to me, because integers should be value types.

ashes999
  • 9,925
  • 16
  • 73
  • 124
  • 1
    You've just discovered how closures work! :-) http://en.wikipedia.org/wiki/Closure_%28computer_science%29 – joce May 06 '11 at 22:13
  • possible duplicate of [C# Captured Variable In Loop](http://stackoverflow.com/questions/271440/c-sharp-captured-variable-in-loop) – nawfal Nov 02 '13 at 06:12

2 Answers2

2

I believe you understand WHY this happens. The problem is it captures the variable i itself, not its value. The workaround that seems better (without toString and int.parse) to me is to declare another local var that copies i

for (int i = 1; i <= n; i++) {
Button b = new Button();
  b.Caption = "Value " + n; // non-WPF: b.Text = "Value " + n;
  int locali = i;
  b.Click += (sender, event) => {
    someFunction(locali);
  }
}

This way the captured variable will be locali and it will remain the same across the loop.

stormbreaker
  • 848
  • 13
  • 22
  • 1
    This is correct in spirit, but `locali` should be *outside* of the delegate, not inside. – JSBձոգչ May 06 '11 at 21:10
  • @JSBangs why should it be declared outside? The scope is limited to the delegate call. – ashes999 May 06 '11 at 21:13
  • 1
    @JSBangs you are absolutely right. I guess I didn't pay much attention when I put it between the other code. @ashes999 It should be outside because if it's in the delegate the `i` will still get captured and ultimately the result will be the same. – stormbreaker May 06 '11 at 21:17
  • As much as I like this solution, this seems like a hack/trick/work-around and not a proper solution. Is there a better way of doing this? (I'll accept this if it's the only answer and it works.) – ashes999 May 06 '11 at 21:19
  • You can read this. http://msdn.microsoft.com/en-us/library/bb763133.aspx It's for VB but the theory is the same, they work the same way. The local variable way is also the microsoft's solution of the problem (the unexpected behavour). EDIT: Another link: http://rongchaua.net/blog/c-lambda-expressions-and-access-to-modified-closure/ – stormbreaker May 06 '11 at 21:25
1

Stormbreaker is correct, for more information see Eric Lippert's answer to this question

C# lambda, local variable value not taken when you think?

Community
  • 1
  • 1
asawyer
  • 17,642
  • 8
  • 59
  • 87