2013-02-16 37 views
82

我得到以下警告:访问的foreach变量封警告

访问的foreach在封闭的可变。使用不同版本的编译器编译时可能会有不同的行为。

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

abovementioned error message in a hover popup

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

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

+4

http://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach – 2013-02-16 07:22:15

+1

TL; DR答案:添加.ToList()或.ToArray()在你的查询表达式的末尾,它将摆脱警告 – JoelFan 2015-08-25 15:18:50

回答

134

此警告有两个部分。首先是...在封闭

...这是不是无效本身

访问的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的更改有助于防止此问题,但不完全。

+6

我已经尝试了使用相同的目标(.Net 3.5)获得不同结果的不同编译器的foreach循环。我使用VS2010(反过来使用与.net 4.0相关的编译器,我相信)和VS2012(我相信.net 4.5编译器)。原则上,这意味着如果您使用VS2013并编辑一个针对.Net 3.5的项目,并将其构建在已安装稍旧框架的构建服务器上,则可以看到您的计算机上的程序与部署的构建有不同的结果。 – Ykok 2014-03-13 13:27:57

+0

很好的答案,但不确定“foreach”是如何相关的。这不会发生与手动枚举,或甚至简单的(int i = 0;我 Brad 2014-07-14 12:53:29

+0

这里的'foreach'这个东西来自于这个问题的内容。你说得对,它可以以各种更一般的方式发生。 – 2014-07-14 12:57:59

12

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

因为在您的示例代码中,reflectModel被分配了一个IEnumerable,而这只会在枚举时进行评估,并且枚举本身可能发生在循环之外,如果您将reflectModel分配给了某些东西范围更广。

如果更改

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

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

然后reflectedModel将被分配foreach循环内的肯定列表,这样你就不会收到警告,因为枚举肯定会发生在循环内,而不是在循环之外。

+0

我读了很多非常长的解释,并没有为我解决这个问题,然后是一个很短的解决方案。谢谢! – 2015-07-22 20:37:14

+0

我阅读了接受的答案,只是想“如果它没有约束变量,它是如何闭包的?”但现在我明白这是关于什么时候评估发生的,谢谢! – Jerome 2015-08-28 12:13:30

+0

是的,这是显而易见的通用解决方案。缓慢的,内存密集型的,但我认为它对所有情况都是100%的工作。 – 2016-10-26 17:32:14

8

块范围的变量应该解决警告。

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

简短和重点。谢谢... – raider33 2017-04-11 13:56:39