C# – Closures capture variables, not values

Let’s say you’re firing off Task.Run() a bunch of times in a loop and passing in the loop variable, like this:

for (int i = 0; i < 10; i++)
{
	Task.Run(() => Console.WriteLine(i));
}
Code language: C# (cs)

The natural assumption is that this will print out 0 through 9. But instead, it’s printing out ten 10’s:

10
10
10
10
10
10
10
10
10
10Code language: plaintext (plaintext)

This is a common mistake, especially when using Task.Run() in a loop. What’s happening here is the closure is capturing the loop variable, not the value of the loop variable. To capture the value instead, you can create a temp variable and pass that into the closure, like this:

for (int i = 0; i < 10; i++)
{
	var tmp = i;
	Task.Run(() => Console.WriteLine(tmp));
}
Code language: C# (cs)

Now this will print out the value of the loop variable at the time the closure was created (and it happens to be in random order, since Task.Run doesn’t guarantee the order that things get invoked):

2
3
9
7
5
0
1
4
8
6Code language: plaintext (plaintext)

The closure captures the variable, not the value

Closures capture the variable, not the value of the variable. When the variable changes, that change is reflected in the closure. The following code illustrates this:

int j = 1;
Action a = () => Console.WriteLine(j);
a();
j = 2;
a();
Code language: C# (cs)

This outputs the following:

1
2
Code language: plaintext (plaintext)

Can a closure change the original variable’s value?

Yes, the closure can change the captured variable’s value and it will be reflected outside of the closure.

Here’s an example of this:

int j = 1;
Action a = () =>
{
	j = 2;
};
a();

Console.WriteLine(j);
Code language: C# (cs)

This outputs:

2Code language: plaintext (plaintext)

Leave a Comment