9

任何人都可以解释为什么这个片段:

// 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 变量。使用不同版本的编译器编译时可能会有不同的行为。” 为什么它可能有不同的行为?

4

2 回答 2

16

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

由于 foreach 中的循环变量受到闭包影响的方式,C# 4 和 C# 5 之间发生了重大变化,特别是自 C# 3 中引入 lambda 表达式以来。Resharper 警告您这一点,以防您可能依赖或以其他方式已经开始期待以前的语义。

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

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

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

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

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

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

于 2013-04-05T15:07:59.343 回答
2

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

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

于 2013-04-05T15:07:55.367 回答