在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)); }