简体   繁体   English

在关闭警告中访问foreach变量

[英]Access to foreach variable in closure warning

I'm getting the following warning: 我收到以下警告:

Access to foreach variable in closure. 在关闭时访问foreach变量。 May have different behaviour when compiled with different versions of compiler. 使用不同版本的编译器编译时可能会有不同的行为。

This is what it looks like in my editor: 这就是我的编辑器中的样子:

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

I know how fix this warning, but I want know why would I get this warning? 我知道怎么修这个警告,但我想知道为什么我会收到这个警告?

Is this about the "CLR" version? 这是关于“CLR”版本的吗? Is it related to "IL"? 它与“IL”有关吗?

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 foreach 1 : 接受您的查询,注意您摘录的代码基本上是C#编译器(在C#5之前)为foreach 1生成的扩展形式:

I [don't] understand why [the following is] not valid: 我[不]理解为什么[以下]无效:

 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 value of s then everything is good. 如果你在循环中所做s一切都是使用s那么一切都很好。 But closing over s will lead to counter-intuitive behaviour. 但是关闭s将导致反直觉的行为。 Take a look at the following code: 看看下面的代码:

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 after the 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). 因为我们创建了五个函数,它们完全相同:打印s的值(我们已经关闭了)。 In reality, they're the same function ("Print s ", "Print s ", "Print 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 . 在我们使用它们的时候,它们正是我们所要求的:打印s的值。 If you look at the last known value of s , you'll see that it's 5 . 如果你看一下s的最后一个已知值,你会发现它是5 So we get s == 5 printed five times to the console. 所以我们将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". Eric Lippert的帖子称这种变化发生在“C#5”中。 So 所以 presumably you have to target .NET 4.5 or later 大概你必须以.NET 4.5或更高版本为目标 with a C# 5 or later compiler to get the new behaviour, and everything before that gets the old behaviour. 使用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的后果。

1 foreach is a much more common construct than the code you've posted in your comment. 1 foreach是一个比你在评论中发布的代码更常见的结构。 The issue typically arises through use of foreach , not through manual enumeration. 这个问题通常是通过使用foreach而不是通过手动枚举产生的。 That's why the changes to foreach in C# 5 help prevent this issue, but not completely. 这就是为什么C#5中foreach的变化有助于防止这个问题,但并非完全无法解决。

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. 您收到警告,因为在您的示例代码中,reflectModel被分配了一个IEnumerable,它只会在枚举时进行评估,如果您将reflectModel分配给范围更广的内容,枚举本身可能会发生在循环之外。

If you changed 如果你改变了

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

to

...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. 那么reflectModel将在foreach循环中被分配一个明确的列表,所以你不会收到警告,因为枚举肯定会在循环内发生,而不是在它之外。

A block-scoped variable should resolve the warning. 块范围的变量应解决警告。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM