Understanding Local Function Capturing of Variables inside Loops

630 views Asked by At

I've noticed the following. This C# code:

        List<Action> methodList = new List<Action>();
        for (int iFn = 0; iFn < 4; ++iFn)
        {
            void thisLocalFunction()
            {
                string output = iFn.ToString();
                Console.WriteLine(output);
            }
            methodList.Add(thisLocalFunction);
        }
        
        for (int iFn = 0; iFn < methodList.Count; ++iFn)
        {
            methodList[iFn]();
        }

Produces 4, 4, 4, 4. On the other hand, this code:

        List<Action> methodList = new List<Action>();
        for (int iFn = 0; iFn < 4; ++iFn)
        {
            string output = iFn.ToString();
            void thisLocalFunction()
            {
                Console.WriteLine(output);
            }
            methodList.Add(thisLocalFunction);
        }
        
        for (int iFn = 0; iFn < methodList.Count; ++iFn)
        {
            methodList[iFn]();
        }

Produces 0, 1, 2, 3.

I'm not sure I understand why. I've read a bit about "capturing" but I'm not sure if that's related to what's going on here. Could someone give me a breakdown of why the two implementations behave differently?

1

There are 1 answers

3
Caius Jard On BEST ANSWER

The critical bit of code that you've moved, string output = iFn.ToString(); turns iFn into a string when it is run

In the first example, it is run after the loop has finished (but "by magic" a single iFn is still available). Of course, after the loop finishes, iFn is 4 (because that's how the loop stopped). Why is it run after the loop finishes? because you "created a method and stored it in a variable" and the call to turn iFn into a string is inside this method. You didn't run the method you created while you were in the loop, so iFn was never turned into a string while you were in the loop. The method-in-a-variable was only run afterwards, and because the method contains the code that turns iFn into a string, it accesses iFn at whatever current value it has, which is 4.

In the second example iFn is turned into a string as the loop is executing so the value is 0, then 1, then 2, then 3. This is stored in a new variable (called output) that the method you created in the loop, has access to (again, let's say the method can still access output, even though it looks like it's out of scope, "by magic"), but the value of output is generated on each pass of the loop and then given to the method. In essence each of your 4 methods-as-a-variable stored in the list has access to a different variable called output - the first method' output has a value of 0, the second method's variable called output has a value of 1..

You could conceive that when you create a method that you will later invoke, the "environment variables" the method had access to at the time it was created, are packaged up with it so that it has its own little environment to execute in. In the first case, each of the 4 methods has access to iFn at whatever value it is now, whereas in the second they have access to output at whatever value it had when it was in the loop. Just because the variable is named the same on each pass of the loop doesn't mean it's reusing the same memory location to hold the data