在closures警告中访问foreachvariables

我收到以下警告:

在闭包中访问foreachvariables。 使用不同版本的编译器编译时可能会有不同的行为。

这是在我的编辑器中看起来像:

上述悬停弹出窗口中的错误信息

我知道如何解决这个警告,但我想知道为什么我会得到这个警告?

这是关于“CLR”版本吗? 它与“IL”有关吗?

这个警告有两个部分。 首先是…

在闭包中访问foreachvariables

…这本身并不是无效的,但乍一看是非直觉的。 做对也很困难。 (以至于我在下面链接的文章将其描述为“有害的”)。

拿你的查询,注意你所摘录的代码基本上是C#编译器(在C#5之前)为foreach 1生成的扩展forms:

我不明白为什么[以下是]无效:

 string s; while (enumerator.MoveNext()) { s = enumerator.Current; ... 

那么,它在语法上是有效的。 如果你在循环中所做的只是使用s ,那么一切都是好的。 但是closuress会导致违反直觉的行为。 看看下面的代码:

 var countingActions = new List<Action>(); var numbers = from n in Enumerable.Range(1, 5) select n.ToString(CultureInfo.InvariantCulture); using (var enumerator = numbers.GetEnumerator()) { string s; while (enumerator.MoveNext()) { s = enumerator.Current; Console.WriteLine("Creating an action where s == {0}", s); Action action = () => Console.WriteLine("s == {0}", s); countingActions.Add(action); } } 

如果你运行这个代码,你会得到下面的控制台输出:

 Creating an action where s == 1 Creating an action where s == 2 Creating an action where s == 3 Creating an action where s == 4 Creating an action where s == 5 

这是你所期望的。

要查看您可能不期望的内容,请在上面的代码之后立即运行以下代码:

 foreach (var action in countingActions) action(); 

您将获得以下控制台输出:

 s == 5 s == 5 s == 5 s == 5 s == 5 

为什么? 因为我们创build了五个函数,它们完全相同:打印s的值(我们已经closures了)。 实际上,它们是相同的function(“打印s ”,“打印s ”,“打印s ”…)。

在我们去使用它们的时候,他们正是按照我们所要求的:打印s的值。 如果你看看s的最后已知值,你会看到它是5 。 所以我们把s == 5打印五次到控制台。

这正是我们要求的,但可能不是我们想要的。

警告的第二部分

使用不同版本的编译器编译时可能会有不同的行为。

…就是这样。 从C#5开始,编译器会生成不同的代码,通过foreach “防止”这种情况发生 。

因此下面的代码会在不同版本的编译器下产生不同的结果:

 foreach (var n in numbers) { Action action = () => Console.WriteLine("n == {0}", n); countingActions.Add(action); } 

因此,它也会产生R#警告:)

上面的第一个代码片段将在所有版本的编译器中performance出相同的行为,因为我没有使用foreach (而是将其扩展为C#5之前的编译器)。

这是CLR版本吗?

我不太清楚你在这里问什么。

Eric Lippert的文章说这个变化在“C#5”中发生了。 所以 据推测你必须瞄准.NET 4.5或更高版本 用C#5或更高版本的编译器来获取新的行为,以及之前的所有内容都会得到旧的行为。

但要清楚的是,这是编译器的function,而不是.NET Framework版本。

与IL有关吗?

不同的代码会产生不同的IL,所以在这个意义上说IL会产生后果。

1 foreach比你在评论中发布的代码更常见。 这个问题通常是通过使用foreach ,而不是通过手动枚举。 这就是为什么在C#5中对foreach的更改有助于防止此问题,但不完全。

第一个答案很好,所以我想我只是添加一件事情。

您会收到警告,因为在您的示例代码中,reflectModel被分配了一个IEnumerable,只有在枚举时才会对其进行评估,如果您将reflectModel分配给更广泛的范围,则枚举本身可能会发生在循环之外。

如果你改变了

...Where(x => x.Name == property.Value)

...Where(x => x.Name == property.Value).ToList()

那么在foreach循环中,reflection模型将被分配一个确定的列表,所以你不会收到警告,因为枚举肯定会发生在循环内,而不是在它之外。

块范围的variables应该解决警告。

 foreach (var entry in entries) { var en = entry; var result = DoSomeAction(o => o.Action(en)); }