我得到以下警告:访问的foreach变量封警告
访问的foreach在封闭的可变。使用不同版本的编译器编译时可能会有不同的行为。
这就是它看起来像在我的编辑器:
我知道如何解决这个问题的警告,但我想知道为什么我会得到这样的警告?
这是关于“CLR”版本吗?它与“IL”有关吗?
我得到以下警告:访问的foreach变量封警告
访问的foreach在封闭的可变。使用不同版本的编译器编译时可能会有不同的行为。
这就是它看起来像在我的编辑器:
我知道如何解决这个问题的警告,但我想知道为什么我会得到这样的警告?
这是关于“CLR”版本吗?它与“IL”有关吗?
此警告有两个部分。首先是...在封闭
...这是不是无效本身
访问的foreach变量,但它是反直觉的第一眼。做对也很困难。 (以至于我在下面链接的文章将其描述为“有害”)。
请注意,您摘录的代码基本上是C#编译器(在C#5之前)的扩展形式,生成
foreach
:我[不]明白为什么[以下是]无效:
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ...
嗯,这是有效的语法。如果你在循环中所做的只是使用的值的
s
那么一切都很好。但超过s
将导致违反直觉的行为。看看下面的代码: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
为什么?因为我们创建了五个函数,它们都完全相同:打印
s
(我们已经关闭)的值。实际上,它们是相同的功能(“打印s
”,“打印s
”,“打印s
”...)。在我们开始使用它们的时候,它们完全按照我们的要求:打印
s
的值。如果您查看s
的最后一个已知值,您会看到它是5
。所以我们得到s == 5
五次打印到控制台。这正是我们所要求的,但可能不是我们想要的。
警告的第二部分...当使用不同版本的编译器编译
可能有不同的行为。
......就是这样。 Starting with C# 5, the compiler generates different code that "prevents" this from happening via
foreach
。因此,下面的代码将产生在不同版本的编译器不同的结果:
foreach (var n in numbers) { Action action =() => Console.WriteLine("n == {0}", n); countingActions.Add(action); }
因此,还会产生将R#警告:)
我的第一个代码段,上方,将在所有版本的编译器中都表现出相同的行为,因为我没有使用
foreach
(相反,我已经将它扩展到C#5之前的编译器的方式)。这是用于CLR版本吗?
我不太确定你在问什么。
Eric Lippert的文章称这种变化发生在“C#5”中。所以
大概你必须使用C#5或更高版本的编译器将.NET 4.5或更高版本的目标设为以获得新的行为,并且之前的所有内容都会得到旧的行为。但要清楚,它是编译器的功能,而不是.NET Framework版本。
与IL有关吗?
不同的代码会产生不同的IL,因此在这个意义上说IL会产生后果。
foreach
是比您在评论中发布的代码更常见的结构。这个问题通常是通过使用foreach
而不是通过手动枚举产生的。这就是为什么在C#5中对foreach
的更改有助于防止此问题,但不完全。
我已经尝试了使用相同的目标(.Net 3.5)获得不同结果的不同编译器的foreach循环。我使用VS2010(反过来使用与.net 4.0相关的编译器,我相信)和VS2012(我相信.net 4.5编译器)。原则上,这意味着如果您使用VS2013并编辑一个针对.Net 3.5的项目,并将其构建在已安装稍旧框架的构建服务器上,则可以看到您的计算机上的程序与部署的构建有不同的结果。 – Ykok 2014-03-13 13:27:57
很好的答案,但不确定“foreach”是如何相关的。这不会发生与手动枚举,或甚至简单的(int i = 0;我
这里的'foreach'这个东西来自于这个问题的内容。你说得对,它可以以各种更一般的方式发生。 – 2014-07-14 12:57:59
第一个答案很好,所以我想我只是添加一件事。
因为在您的示例代码中,reflectModel被分配了一个IEnumerable,而这只会在枚举时进行评估,并且枚举本身可能发生在循环之外,如果您将reflectModel分配给了某些东西范围更广。
如果更改
...Where(x => x.Name == property.Value)
到
...Where(x => x.Name == property.Value).ToList()
然后reflectedModel将被分配foreach循环内的肯定列表,这样你就不会收到警告,因为枚举肯定会发生在循环内,而不是在循环之外。
我读了很多非常长的解释,并没有为我解决这个问题,然后是一个很短的解决方案。谢谢! – 2015-07-22 20:37:14
我阅读了接受的答案,只是想“如果它没有约束变量,它是如何闭包的?”但现在我明白这是关于什么时候评估发生的,谢谢! – Jerome 2015-08-28 12:13:30
是的,这是显而易见的通用解决方案。缓慢的,内存密集型的,但我认为它对所有情况都是100%的工作。 – 2016-10-26 17:32:14
块范围的变量应该解决警告。
foreach (var entry in entries)
{
var en = entry;
var result = DoSomeAction(o => o.Action(en));
}
简短和重点。谢谢... – raider33 2017-04-11 13:56:39
http://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach – 2013-02-16 07:22:15
TL; DR答案:添加.ToList()或.ToArray()在你的查询表达式的末尾,它将摆脱警告 – JoelFan 2015-08-25 15:18:50