在闭包中Foreach变量.为什么这些片段的结果不同?

本文关键字:片段 结果 闭包 Foreach 变量 为什么 | 更新日期: 2023-09-27 18:07:54

谁能解释一下为什么这段代码:

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

与下面的不同:

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

在第一个片段中,所有任务都以自己的消息开始,而在第二个片段中,所有任务都以相同的消息开始?

Resharper给出了这样的描述:"访问闭包中的foreach变量。使用不同版本的编译器编译时可能会有不同的行为。为什么它会有不同的行为?

在闭包中Foreach变量.为什么这些片段的结果不同?

Resharper给出了这样的描述:"访问闭包中的foreach变量。使用不同版本的编译器编译时可能会有不同的行为。为什么它会有不同的行为?

在c# 4和c# 5之间有一个重大的变化,因为foreach中的循环变量受到闭包的影响,特别是在c# 3引入lambda表达式之后。Resharper警告你这一点,以防你可能依赖于或以其他方式期望使用前一种语义。

在c# 4中,循环变量在每次循环迭代之间共享,闭包捕获该变量,因此当大多数人关闭循环变量时,会导致意想不到的结果。

在c# 5中,循环的每次迭代都有自己的变量,因此一次迭代中的闭包不会像其他迭代那样关闭相同的变量,从而导致更多预期的结果(对大多数人来说)。

这让我们找到了问题的核心:

在第一个片段中,所有任务都以自己的消息开始,而在第二个片段中,所有任务都以相同的消息开始?

在第一个代码片段中,您在循环中创建了循环变量的副本,并且闭包发生在内部变量上。在第二种情况下,直接关闭循环变量。假设您在c# 4下运行,因此适用前一种语义。如果在c# 5中运行,两个版本的循环输出应该是一致的。这就是Resharper所指的改变,它也应该让你理解如何在c# 4中构建你的代码(也就是说,使用你写的第一个版本)。

正如Justin Pihony在评论中指出的那样,Eric Lippert写了一篇非常有用的关于前语义的博客文章,其中也暗示了c# 5的变化。

第二个代码示例有一个单个 messageToSend,所有lambda表达式都在它们的闭包中捕获它。

当委托运行时,它们使用变量的当前