Captured Closure (Loop Variable) in C# 5.0

Captured Closure (Loop Variable) in C# 5.0

What is the reasoning behind this?

I'm going to assume you mean "why wasn't it changed for for loops as well?"

The answer is that for for loops, the existing behaviour makes perfect sense. If you break a for loop into:

  • initializer
  • condition
  • iterator
  • body

... then the loop is roughly:

{
initializer;
while (condition)
{
body;
iterator;
}
}

(Except that the iterator is executed at the end of a continue; statement as well, of course.)

The initialization part logically only happens once, so it's entirely logical that there's only one "variable instantiation". Furthermore, there's no natural "initial" value of the variable on each iteration of the loop - there's nothing to say that a for loop has to be of a form declaring a variable in the initializer, testing it in the condition and modifying it in the iterator. What would you expect a loop like this to do:

for (int i = 0, j = 10; i < j; i++)
{
if (someCondition)
{
j++;
}
actions.Add(() => Console.WriteLine(i, j));
}

Compare that with a foreach loop which looks like you're declaring a separate variable for every iteration. Heck, the variable is read-only, making it even more odd to think of it being one variable which changes between iterations. It makes perfect sense to think of a foreach loop as declaring a new read-only variable on each iteration with its value taken from the iterator.

Captured variable in a loop in C#

Yes - take a copy of the variable inside the loop:

while (variable < 5)
{
int copy = variable;
actions.Add(() => copy * 2);
++ variable;
}

You can think of it as if the C# compiler creates a "new" local variable every time it hits the variable declaration. In fact it'll create appropriate new closure objects, and it gets complicated (in terms of implementation) if you refer to variables in multiple scopes, but it works :)

Note that a more common occurrence of this problem is using for or foreach:

for (int i=0; i < 10; i++) // Just one variable
foreach (string x in foo) // And again, despite how it reads out loud

See section 7.14.4.2 of the C# 3.0 spec for more details of this, and my article on closures has more examples too.

Note that as of the C# 5 compiler and beyond (even when specifying an earlier version of C#), the behavior of foreach changed so you no longer need to make local copy. See this answer for more details.

Captured Closure (for Loop Variable) in Go

Do you want the closure over the variable or the value? For example,

package main

import "fmt"

func VariableLoop() {
f := make([]func(), 3)
for i := 0; i < 3; i++ {
// closure over variable i
f[i] = func() {
fmt.Println(i)
}
}
fmt.Println("VariableLoop")
for _, f := range f {
f()
}
}

func ValueLoop() {
f := make([]func(), 3)
for i := 0; i < 3; i++ {
i := i
// closure over value of i
f[i] = func() {
fmt.Println(i)
}
}
fmt.Println("ValueLoop")
for _, f := range f {
f()
}
}

func VariableRange() {
f := make([]func(), 3)
for i := range f {
// closure over variable i
f[i] = func() {
fmt.Println(i)
}
}
fmt.Println("VariableRange")
for _, f := range f {
f()
}
}

func ValueRange() {
f := make([]func(), 3)
for i := range f {
i := i
// closure over value of i
f[i] = func() {
fmt.Println(i)
}
}
fmt.Println("ValueRange")
for _, f := range f {
f()
}
}

func main() {
VariableLoop()
ValueLoop()
VariableRange()
ValueRange()
}

Output:


VariableLoop
3
3
3
ValueLoop
0
1
2
VariableRange
2
2
2
ValueRange
0
1
2

References:

The Go Programming Language Specification

Function literals

Function literals are closures: they may refer to variables defined in
a surrounding function. Those variables are then shared between the
surrounding function and the function literal, and they survive as
long as they are accessible.

Go FAQ: What happens with closures running as goroutines?

To bind the current value of v to each closure as it is launched, one
must modify the inner loop to create a new variable each iteration.
One way is to pass the variable as an argument to the closure.

Even easier is just to create a new variable, using a declaration
style that may seem odd but works fine in Go.

What is loop variable closure?

Take a simple foreach loop:

foreach(var i in Enumerable.Range(1,10))
{
Console.WriteLine(i);
}

Before C# 5, there was just one variable i that was used for the entire run (every iteration) of the loop.

After the introduction of C# 5, this loop will now have 10 separate i variables... a different one for each iteration of the loop. These variables have shorter lifetimes and all have the same name, but they are still unique variables.1

At first glance the difference may not seem to matter, but when you're working with a closure the older behavior could have unexpected results.

You can see the difference with this code sample:

var results = new List<Func<int>>();
foreach(var i in Enumerable.Range(1,10))
{
results.Add(() => i);
}
foreach(var item in results)
{
Console.WriteLine(item());
}

This code will compile and run from both VS 2010 and VS 2012 (and later), but will give different results depending on which you used.

A closure is what happens when the compiler "closes over" or "hoists" a variable out of it's normal scope region and extends it's lifetime. Whenever you're working with a lambda expression (indicated by the => operator), there's very often a closure involved as well. In my sample, the variable i is used in a closure to be available to the anonymous functions placed in the list.

It's important to understand the distinction between the variable and value. If you can run the code in VS2010, when there was only one variable, the distinction is very clear... the value in the variable from earlier iterations of the loop can continue to be modified by later iterations of the loop, before the function is called, and so the code would output all 10s. VS2012 and later users separate variables, and so will output 1 through 10 in sequence.


1 Before anyone asks: there is no performance difference. The difference is a compiler abstraction detail that may affect performance, but usually not in any measurable way.

Does .net 5 compiler give warning for the loop-variable-closure issue?

It seems you are confusing C# versions and .Net versions. The latest .Net version is 4.5 and the latest C# one is 5.0.

The breaking change fix only exists in C# version 5.0 and it only affects the foreach loop. As Console explained you would only need to worry about it if you were relying on that bug being there, which isn't likely. You can however safely use the latest .Net with an old C# version (i.e. 4.0).

If you do end up using C# version 5.0 you won't get a warning, because there's nothing to warn about anymore. The bug was fixed, even though it was a small breaking change.

Closure captured variable modifies the original as well

The outside variable and the variable in the closure are the same variable. Your program is equivalent to:

private class Closure
{
public int j;
public int Method()
{
for (int i = 0; i < 3; i++)
{
this.j += i;
}
return this.j;
}
}
static void Main(string[] args)
{
Closure closure = new Closure();
closure.j = 0;
Func<int> f = closure.Method;
int myStr = f();
Console.WriteLine(myStr);
Console.WriteLine(closure.j);
Console.Read();
}

Now is it clear why you get the observed result?

Foreach variable in closure

.Net 4.0 is irrelevant here. Only thing is the c# compiler. Starting from C# 5.0 behavior is changed. I presume you're using C# 5.0 compiler.

This means that even in .Net 2.0 this code will work if you're using Visual studio 2012 (given that default C# compiler version is 5.0)

If you're using Visual studio 2012 or newer version by default C#5.0 compiler will be used and hence you don't see the bug.

Lambda variable capture in loop - what happens here?

In this line

 listActions.Add(() => Console.WriteLine(i));

the variable i, is captured, or if you wish, created a pointer to the memory location of that variable. That means that every delegate got a pointer to that memory location. After this loop execution:

foreach (int i in Enumerable.Range(1, 10))
{
listActions.Add(() => Console.WriteLine(i));
}

for obvious reasons i is 10, so the memory content that all pointers present in Action(s) are pointing, becomes 10.

In other words, i is captured.

By the way, should note, that according to Eric Lippert, this "strange" behaviour would be resolved in C# 5.0.

So in the C# 5.0 your program would print as expected:

1,2,3,4,5...10

EDIT:

Can not find Eric Lippert's post on subject, but here is another one:

Closure in a Loop Revisited



Related Topics



Leave a reply



Submit