1

I want to create some Box objects inside an array. In the begining I initialized these objects in a for-loop, attaching also an event handler which passes the current index i.

int[] boxes = new int[9];
for (int i = 0; i < boxes.Length; i++)
{
  var b = new Box();
  b.MarkingOccupiedEvent += (s, e) => OnBoxMarkOccupied(i, e.Marking);
  boxes[i] = b;
}
private void OnBoxMarkOccupied(int position, Marking marking)
{
  BoxOccupiedEvent?.Invoke(this, new BoxMarkingEventArgs(position, marking));
}

I am expecting that when MarkingOccupiedEvent of boxes[0] is raised then I am going to retrieve a position equals to 0 from BoxMarkingEventArgs. All I am getting though is the value of 9. And this applies to all boxes inside the array, not just the first one.

Out of curiosity I deleted the code in the loop and wrote the initialization code by hand (since it's only 9 boxes),

  var b = new Box();
  b.MarkingOccupiedEvent += (s, e) => OnBoxMarkOccupied(0, e.Marking);
  boxes[0] = b;

  b = new Box();
  b.MarkingOccupiedEvent += (s, e) => OnBoxMarkOccupied(1, e.Marking);
  boxes[1] = b;

  b = new Box();
  b.MarkingOccupiedEvent += (s, e) => OnBoxMarkOccupied(2, e.Marking);
  boxes[2] = b;

  b = new Box();
  b.MarkingOccupiedEvent += (s, e) => OnBoxMarkOccupied(3, e.Marking);
  boxes[3] = b;

  b = new Box();
  b.MarkingOccupiedEvent += (s, e) => OnBoxMarkOccupied(4, e.Marking);
  boxes[4] = b;

  b = new Box();
  b.MarkingOccupiedEvent += (s, e) => OnBoxMarkOccupied(5, e.Marking);
  boxes[5] = b;

  b = new Box();
  b.MarkingOccupiedEvent += (s, e) => OnBoxMarkOccupied(6, e.Marking);
  boxes[6] = b;

  b = new Box();
  b.MarkingOccupiedEvent += (s, e) => OnBoxMarkOccupied(7, e.Marking);
  boxes[7] = b;

  b = new Box();
  b.MarkingOccupiedEvent += (s, e) => OnBoxMarkOccupied(8, e.Marking);
  boxes[8] = b;

Guess what, that code works!

What is wrong with the approach using the for-loop?

Themelis
  • 4,048
  • 2
  • 21
  • 45
  • 3
    `i` is capture in the event definition in the foor loop. When the delegates are invoked, i = 9 because the for loop has ended. More details here: https://csharpindepth.com/articles/Closures – vc 74 Apr 08 '20 at 15:40
  • Do you mean when the event is fired? @vc74 – Themelis Apr 08 '20 at 15:42
  • Which version of c# are you using? – Chris Dunaway Apr 08 '20 at 15:42
  • I am using .NetCore 2.1 @ChrisDunaway – Themelis Apr 08 '20 at 15:44
  • Create a variable within the scope of the loop (`var index = i;`) and pass that to the lambda. As it is, you are capturing the variable `i`, and it's value will be what it is at the end of the loop for every invocation – Flydog57 Apr 08 '20 at 15:44
  • @Themelis When you define your delegates, i is captured (the variable, not its value). When a delegate is invoked, after the end of the loop, i = 9 because the loop has already ended. – vc 74 Apr 08 '20 at 15:45
  • Wait a minute guys, aren't integers passed by value and not by reference? – Themelis Apr 08 '20 at 15:47
  • Yeah that solved my question @DavidBrowne-Microsoft! – Themelis Apr 08 '20 at 15:49
  • 2
    Yes, but variables are "captured" in a closure, they aren't "passed" to it. When you create a closure around a lambda, a little hidden class is created to manage state. It's that state that's biting you – Flydog57 Apr 08 '20 at 15:49
  • You made it clear to me @Flydog57, thanks – Themelis Apr 08 '20 at 15:50
  • 2
    They are, but C# creates class behind the scenes to keep the context the delegate was defined in so that it is available when the delegate is finally invoked. This context has a **reference** to i which value, in your case, is useless after the for loop has finished. You can use another variable as suggested by @Flydog57 to avoid this 'issue'. – vc 74 Apr 08 '20 at 15:51
  • 2
    @vc74 - Wasn't there a breaking change regarding variable capture in a closure in the latest versions of C#? I thought that in later versions, the loop variable was treated as if it were defined inside the loop. UPDATE: nevermind, that change only applies to `foreach` loops and not `for`. – Chris Dunaway Apr 08 '20 at 16:08
  • 1
    @ChrisDunaway Indeed, in C#5. This is well documented on Eric Lippert's blog: https://ericlippert.com/2009/11/ – vc 74 Apr 08 '20 at 16:47

0 Answers0