【发布时间】:2023-03-21 09:39:01
【问题描述】:
我收到以下警告:
在闭包中访问 foreach 变量。使用不同版本的编译器编译时可能会有不同的行为。
这是它在我的编辑器中的样子:
我知道如何解决此警告,但我想知道为什么会收到此警告?
这是关于“CLR”版本的吗?和“IL”有关系吗?
【问题讨论】:
-
TL;DR 回答:在查询表达式的末尾添加 .ToList() 或 .ToArray() 它将消除警告
我收到以下警告:
在闭包中访问 foreach 变量。使用不同版本的编译器编译时可能会有不同的行为。
这是它在我的编辑器中的样子:
我知道如何解决此警告,但我想知道为什么会收到此警告?
这是关于“CLR”版本的吗?和“IL”有关系吗?
【问题讨论】:
此警告分为两部分。第一个是……
在闭包中访问 foreach 变量
...这本身并不是无效的,但乍一看是违反直觉的。做对也很难。 (以至于我在下面链接到的文章将其描述为“有害”。)
进行查询,注意您摘录的代码基本上是 C# 编译器(在 C# 5 之前)为 foreach1 生成的扩展形式:
我 [不] 明白为什么 [以下内容] 无效:
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。
这正是我们要求的,但可能不是我们想要的。
警告的第二部分...
使用不同版本的编译器编译时可能会有不同的行为。
因此下面的代码在不同版本的编译器下会产生不同的结果:
foreach (var n in numbers)
{
Action action = () => Console.WriteLine("n == {0}", n);
countingActions.Add(action);
}
因此,它也会产生 R# 警告 :)
上面我的第一个代码 sn-p 将在所有版本的编译器中表现出相同的行为,因为我没有使用 foreach(相反,我已经按照 C# 5 之前的编译器的方式对其进行了扩展)。
这是针对 CLR 版本的吗?
我不太清楚你在这里问什么。
Eric Lippert 的帖子说更改发生在“C# 5”中。所以大概你必须以 .NET 4.5 或更高版本为目标,使用 C# 5 或更高版本的编译器来获得新的行为,而在此之前的一切都会获得旧的行为。
但需要明确的是,它是编译器的功能,而不是 .NET Framework 版本。
与 IL 有关系吗?
不同的代码会产生不同的 IL,因此从这个意义上说,生成的 IL 会产生影响。
1foreach 是比您在评论中发布的代码更常见的构造。该问题通常是通过使用foreach 而出现的,而不是通过手动枚举。这就是为什么在 C# 5 中对 foreach 的更改有助于防止这个问题,但不是完全的。
【讨论】:
foreach的东西来自问题的内容。你是对的,它可以以各种更普遍的方式发生。
第一个答案很好,所以我想我只添加一个东西。
您收到警告是因为,在您的示例代码中,reflectModel 被分配了一个 IEnumerable,它只会在枚举时进行评估,如果您将 reflectModel 分配给具有范围更广。
如果你改变了
...Where(x => x.Name == property.Value)
到
...Where(x => x.Name == property.Value).ToList()
然后,reflectModel 将在 foreach 循环内被分配一个明确的列表,因此您不会收到警告,因为枚举肯定会发生在循环内,而不是在循环外。
【讨论】:
块范围的变量应该解决警告。
foreach (var entry in entries)
{
var en = entry;
var result = DoSomeAction(o => o.Action(en));
}
【讨论】: