在关闭警告中访问foreach变量

时间:2013-02-16 07:19:56

标签: c# .net

我收到以下警告:

  

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

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

abovementioned error message in a hover popup

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

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

3 个答案:

答案 0 :(得分:135)

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

  

在闭包中访问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);
    }
}

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

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打印五次到控制台。

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

警告的第二部分......

  

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

......就是这样。 Starting with C# 5, the compiler generates different code that "prevents" this from happening via foreach

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

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或更高版本的编译器来定位.NET 4.5或更高版本才能获得新的行为,之前的所有内容都会产生旧的行为。

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

  

与IL有关系吗?

不同的代码产生不同的IL,因此在这种意义上会产生IL的后果。

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

答案 1 :(得分:12)

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

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

如果你改变了

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

UIView

然后reflectModel会在foreach循环中被分配一个明确的列表,所以你不会收到警告,因为枚举肯定会在循环中发生,而不是在它之外。

答案 2 :(得分:8)

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

public class LayOutTwo extends Fragment implements OnButtonPressListener{
 ViewGroup root;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
    root = (ViewGroup) inflater.inflate(R.layout.layout_two, null);
    return root;
}
@Override
public void onButtonPressed(String msg,int i,ArrayList<object> obj) {
    // TODO Auto-generated method stub
     //print the values or show them
}