C# 访问关闭警告中的foreach变量

C# 访问关闭警告中的foreach变量,c#,.net,C#,.net,我得到以下警告: 在闭包中访问foreach变量。使用不同版本的编译器编译时,可能会有不同的行为 这是我的编辑器中的外观: 我知道如何修复此警告,但我想知道为什么会收到此警告 这是关于“CLR”版本的吗?它与“IL”有关吗?此警告分为两部分。第一个是 在闭包中访问foreach变量 …这本身并不无效,但乍一看是违反直觉的。这也很难做到正确。(如此之多以至于我在下面链接的文章将其描述为“有害的”。) 以您的查询为例,注意您摘录的代码基本上是C编译器(在C#5之前)为foreach1生成的扩展形式

我得到以下警告:

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

这是我的编辑器中的外观:

我知道如何修复此警告,但我想知道为什么会收到此警告


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

此警告分为两部分。第一个是

在闭包中访问foreach变量

…这本身并不无效,但乍一看是违反直觉的。这也很难做到正确。(如此之多以至于我在下面链接的文章将其描述为“有害的”。)

以您的查询为例,注意您摘录的代码基本上是C编译器(在C#5之前)为
foreach
1生成的扩展形式:

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

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);
    }
}
这就是你所期望的

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

foreach (var action in countingActions)
    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
s == 5
s == 5
s == 5
s == 5
s == 5
为什么??因为我们创建了五个函数,它们都做完全相同的事情:打印
s
(我们已经结束了)的值。实际上,它们是相同的功能(“打印
s
”、“打印
s
”、“打印
s
”…)

在我们使用它们的时候,它们完全按照我们的要求执行:打印
s
的值。如果查看
s
的最后一个已知值,您将看到它是
5
。因此,我们将
s==5
打印五次到控制台

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

警告的第二部分

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

……就是这样

因此,以下代码将在不同版本的编译器下产生不同的结果:

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或更高版本的编译器以.NET4.5或更高版本为目标,以获得新的行为,以及之前的所有内容都将获得旧的行为

但要明确的是,它是编译器的函数,而不是.NET Framework版本

与IL有关联吗

不同的代码会产生不同的IL,所以从这个意义上讲,生成的IL会有后果


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

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

您得到警告是因为,在示例代码中,reflectedModel被分配了一个IEnumerable,该IEnumerable仅在枚举时进行计算,如果您将reflectedModel分配给范围更广的对象,则枚举本身可能发生在循环之外

如果你改变了

…其中(x=>x.Name==property.Value)

…其中(x=>x.Name==property.Value).ToList()


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

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

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

TL;DR answer:在查询表达式的末尾添加.ToList()或.ToArray(),这样可以消除警告。实际上,我已经在不同的编译器上尝试过foreach循环,使用相同的目标(.Net 3.5)得到不同的结果。我使用了VS2010(我相信它会使用与.net 4.0相关联的编译器)和VS2012(.net 4.5编译器)。原则上,这意味着如果您正在使用VS2013并编辑一个以.Net 3.5为目标的项目,并在安装了稍旧的框架的构建服务器上构建该项目,那么您可以在您的计算机上看到与部署的构建不同的程序结果。回答不错,但不确定“foreach”的相关程度。手动枚举或者简单的for(inti=0;iforeach内容来自问题的内容。你是对的,它可以以各种更一般的方式发生。为什么R#仍然警告我,它不读目标框架吗?我已将其设置为4.5。“因此,假设你必须以.NET 4.5或更高版本为目标”这句话是不正确的。针对的.NET版本对此没有影响,如果使用C#5(VS 2012或更新版本)进行编译,则.NET 2.0、3.5和4中的行为也会发生更改。这就是为什么你只能在.NET 4.0或更早版本上得到这个警告,如果你的目标是4.5,你就不会得到这个警告,因为你不能在C#4或更旧的编译器上编译4.5。我读了很多没有解决这个问题的很长的解释,然后是一个简短的解释。谢谢我读了被接受的答案,只是想“如果它不绑定变量,那它怎么会是一个闭包?”但现在我明白了,它是关于什么时候进行评估的,那