关于c#:在关闭警告中访问foreach变量

Access to foreach variable in closure warning

我收到以下警告:

Access to foreach variable in closure. May have different behaviour when compiled with different versions of compiler.

在我的编辑器中是这样的:

abovementioned error message in a hover popup

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

这是关于"clr"版本的吗?它与"IL"有关吗?


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

Access to foreach variable in closure

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

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

I [don't] understand why [the following is] not valid:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
    }
}

如果运行此代码,将获得以下控制台输出:

1
2
3
4
5
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

这就是你所期望的。

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

1
2
foreach (var action in countingActions)
    action();

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

1
2
3
4
5
s == 5
s == 5
s == 5
s == 5
s == 5

为什么?因为我们创建了五个完全相同的函数:打印s的值(我们已经结束)。实际上,它们是相同的功能("打印s"、"打印s"、"打印s…"。

在我们使用它们的时候,它们完全按照我们的要求执行:打印s的值。如果你看一下s的最后一个已知值,你会发现它是5。所以我们把s == 5打印了五次到控制台。

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

警告的第二部分…

May have different behaviour when compiled with different versions of compiler.

…就是这样。从C_5开始,编译器生成不同的代码,以"防止"通过foreach发生这种情况。

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

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

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

我上面的第一个代码片段在所有版本的编译器中都会表现出相同的行为,因为我没有使用foreach(相反,我已经像pre-c 5编译器那样扩展了它)。

Is this for CLR version?

我不太确定你在问什么。

埃里克·利珀特的文章说这种变化"发生在C 5"。因此,假设您必须以.NET 4.5或更高版本为目标,使用C 5或更高版本的编译器来获取新行为,以及获取旧行为之前的所有内容。

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

Is there relevance with IL?

不同的代码产生不同的IL,因此从这个意义上说,会对生成的IL产生影响。

1 foreach是一个比您在注释中发布的代码更常见的构造。这个问题通常是通过使用foreach而不是通过手工枚举产生的。这就是为什么C 5中对foreach的更改有助于防止这个问题,但不是完全避免。


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

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

如果你改变了

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

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

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


块范围变量应解决警告。

1
2
3
4
5
foreach (var entry in entries)
{
   var en = entry;
   var result = DoSomeAction(o => o.Action(en));
}