Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Foreach Variable in Closure. Why Results Differ for These Snippets? [duplicate]

Can anybody explain why this snippet:

// Create required tasks
foreach (var messageToSend in messagesToSend)
{
  EmailMessage messageToBeSent = messageToSend;
  Task<bool> processingTask = new Task<bool>(() => SendMessage(messageToBeSent));
  processingTask.Start();
}

Works differently from this one:

// Create required tasks
foreach (var messageToSend in messagesToSend)
{
  Task<bool> processingTask = new Task<bool>(() => SendMessage(messageToSend));
  processingTask.Start();
}

In the first snippet all tasks start with their own message, while in the second one all tasks start with the same message?

Resharper gives this description: "Access to foreach variable in closure. May have different behaviour when compiled with different versions of compiler." Why may it have a different behaviour?

like image 687
Ivan P. Avatar asked Apr 05 '13 15:04

Ivan P.


2 Answers

Resharper gives this description: "Access to foreach variable in closure. May have different behaviour when compiled with different versions of compiler." Why may it have a different behaviour?

There was a breaking change between C# 4 and C# 5 due to the way the loop variable in foreach was impacted by closures, notably since the introduction of lambda expressions in C# 3. Resharper is warning you of this, in case you might depend or otherwise have come to expect the former semantics.

The quick upshot is that in C# 4, the loop variable was shared between each iteration of the loop, and closures capture the variable, so it led to unexpected results for most people when they closed over the loop variable.

In C# 5, each iteration of the loop gets its own variable, so closures in one iteration do not close over the same variable as other iterations, leading to more expected outcomes (for most people).

That gets us to the heart of your problem:

In the first snippet all tasks start with their own message, while in the second one all tasks start with the same message?

In your first snippet, you are creating a copy of the loop variable inside your loop and the closure is occuring over the inner variable. In the second, you close over the loop variable directly. Presumably, you are running under C# 4, so the former semantics apply. If running in C# 5, the loop outputs from both versions should be consistent. This is the change Resharper refers to, and it should also let you understand how to structure your code in C# 4 (namely, use the first version you have written).

As Justin Pihony points out in the comments, Eric Lippert has written a very useful blog article on the former semantics that also alludes to the change for C# 5.

like image 112
Anthony Pegram Avatar answered Oct 19 '22 09:10

Anthony Pegram


Your second code sample has a single messageToSend, which all of the lambda expressions capture in their closures.

When the delegates run, they use the current value of the variable.

like image 21
SLaks Avatar answered Oct 19 '22 10:10

SLaks