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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14907987/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 13:25:44  来源:igfitidea点击:

Access to foreach variable in closure warning

c#.net

提问by

I'm getting the following warning:

我收到以下警告:

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

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

This is what it looks like in my editor:

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

abovementioned error message in a hover popup

悬停弹出窗口中的上述错误消息

I know how fix this warning, but I want know why would I get this warning?

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

Is this about the "CLR" version? Is it related to "IL"?

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

采纳答案by ta.speot.is

There are two parts to this warning. The first is...

这个警告有两个部分。第一个是...

Access to foreach variable in closure

访问闭包中的 foreach 变量

...which is not invalid per se but it is counter-intuitive at first glance. It's also very hard to do right. (So much so that the article I link to below describes this as "harmful".)

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

Take your query, noting that the code you've excerpted is basically an expanded form of what the C# compiler (before C# 5) generates for foreach1:

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

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

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

我 [不] 理解为什么 [以下内容] 无效:

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

Well, it is valid syntactically. And if all you're doing in your loop is using the valueof sthen everything is good. But closing over swill lead to counter-intuitive behaviour. Take a look at the following code:

嗯,它在语法上是有效的。如果你在循环中所做的一切都是使用的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);
    }
}

If you run this code, you'll get the following console output:

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

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

This is what you expect.

这就是你所期望的。

To see something you probably don't expect, run the following code immediately afterthe above code:

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

foreach (var action in countingActions)
    action();

You'll get the following console output:

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

s == 5
s == 5
s == 5
s == 5
s == 5

Why? Because we created five functions that all do the exact same thing: print the value of s(which we've closed over). In reality, they're the same function ("Print s", "Print s", "Print s"...).

为什么?因为我们创建了五个函数,它们都做完全相同的事情:打印s(我们已经关闭)的值。实际上,它们是相同的功能(“打印s”、“打印s”、“打印s”...)。

At the point at which we go to use them, they do exactly what we ask: print the value of s. If you look at the last known value of s, you'll see that it's 5. So we get s == 5printed five times to the console.

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

Which is exactly what we asked for, but probably not what we want.

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

The second part of the warning...

警告的第二部分...

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

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

...is what it is. Starting with C# 5, the compiler generates different code that "prevents" this from happening via foreach.

……原来如此。从 C# 5 开始,编译器生成不同的代码,通过foreach.

Thus the following code will produce different results under different versions of the compiler:

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

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

Consequently, it will also produce the R# warning :)

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

My first code snippet, above, will exhibit the same behaviour in all versions of the compiler, since I'm not using foreach(rather, I've expanded it out the way pre-C# 5 compilers do).

我上面的第一个代码片段将在所有版本的编译器中表现出相同的行为,因为我没有使用foreach(相反,我已经按照 C# 5 之前的编译器所做的方式对其进行了扩展)。

Is this for CLR version?

这是用于 CLR 版本吗?

I'm not quite sure what you're asking here.

我不太确定你在这里问什么。

Eric Lippert's post says the change happens "in C# 5". So presumably you have to target .NET 4.5 or laterwith a C# 5 or later compiler to get the new behaviour, and everything before that gets the old behaviour.

Eric Lippert 的帖子说这种变化发生在“C# 5”中。所以大概你必须针对 .NET 4.5 或更高版本使用 C# 5 或更高版本的编译器来获取新行为,而在此之前的所有内容都会获取旧行为。

But to be clear, it's a function of the compiler and not the .NET Framework version.

但需要明确的是,它是编译器的功能,而不是 .NET Framework 版本。

Is there relevance with IL?

与 IL 有关系吗?

Different code produces different IL so in that sense there's consequences for the IL generated.

不同的代码产生不同的 IL,因此从这个意义上说,产生的 IL 会产生后果。

1foreachis a much more common construct than the code you've posted in your comment. The issue typically arises through use of foreach, not through manual enumeration. That's why the changes to foreachin C# 5 help prevent this issue, but not completely.

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

回答by David

The first answer is great, so I thought I'd just add one thing.

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

You're getting the warning because, in your example code, reflectedModel is being assigned an IEnumerable, which will only be evaluated at the time of enumeration, and enumeration itself could happen outside of the loop if you assigned reflectedModel to something with a broader scope.

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

If you changed

如果你改变了

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

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

to

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

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

then reflectedModel would be assigned a definite list within the foreach loop, so you wouldn't receive the warning, as the enumeration would definitely happen within the loop, and not outside it.

然后反射模型将在 foreach 循环中分配一个明确的列表,因此您不会收到警告,因为枚举肯定会发生在循环内,而不是在循环外。

回答by Dmitry Gogol

A block-scoped variable should resolve the warning.

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

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