重现"关闭foreach变量"疑难杂症

时间:2015-12-16 17:56:03

标签: c# visual-studio visual-studio-2013

使用Visual Studio 2013,我试图重现Eric Lippert的博文"Closing over the loop variable considered harmful"中提到的问题。

在项目属性中,我选择了" C#3.0"作为语言版本(Build> Advanced ...)。此外,我选择了" .NET Framework 3.5"作为目标框架,好像我认为这不应该是必要的,因为这只涉及语言。

运行他的代码:

using System;
using System.Collections.Generic;

namespace Project1
{
    class Class1
    {
        public static void Main(string[] args)
        {
            var values = new List<int>() { 100, 110, 120 };
            var funcs = new List<Func<int>>();
            foreach (var v in values)
            {
                funcs.Add(() => v);
            }
            foreach (var f in funcs)
                Console.WriteLine(f());
        }
    }
}

预期产出:

120
120
120

实际输出:

100
110
120

Eric Lippert himself"Is there a reason for C#'s reuse of the variable in a foreach?"中的回答:

  

for循环不会被更改,并且更改不会被&#34;反向移植&#34;到以前版本的C#。因此,在使用这个习语时你应该继续小心。

我做错了什么?

2 个答案:

答案 0 :(得分:9)

斯科特的答案是正确的,但可以使用一些额外的澄清。

这里的问题是“语言版本”开关没有按照您的想法执行。在我看来,这是一种错误,因为它有很多误导性。 “语言版本”开关并不意味着“使用旧编译器”;它不是兼容模式。

相反,它意味着“使用当前编译器,如果我使用所选语言版本中没有的功能,则会产生错误。”

这种切换的原因是开发团队中的一个人可以“试用”新版本的编译器以确保他们的代码仍然有效,但在他们检查之前知道他们没有意外地使用过他们的队友编译器会扼杀的语言功能。因此,如果您将语言版本设置为3.0,那么“dynamic”将无效(因为它是在C#4.0中添加的),但它仍然是您安装的任何版本的编译器。

Scott指出,如果你想使用旧的编译器,你必须在你的机器上找到旧编译器的副本并明确地使用它。

请参阅http://ericlippert.com/2013/04/04/what-does-the-langversion-switch-do/以获取此交换机执行和不执行操作的更多示例。

答案 1 :(得分:8)

我相信即使您为您的语言选择C#3.0,编译器仍会执行新行为,我认为您不能在新编译器中重新创建旧行为。设置语言的唯一方法是限制您使用语言的更高版本中引入的语言功能。

您必须找到旧编译器(VS 2012或更早版本)的副本才能获得该行为。当Eric说“改变不会”被“移植”到以前版本的C#时,他的意思是他们不会发布VS2012的更新来改变该编译器中的行为(当你引用的帖子写成VS2013刚刚问世时和VS2012仍然广泛使用)。

编辑: 如果你真的想重新创建行为,你需要为每个循环编写自己的手册。

public static void Main(string[] args)
{
    var values = new List<int>() { 100, 110, 120 };
    var funcs = new List<Func<int>>();

    using (var enumerator = values.GetEnumerator())
    {
        int v;
        while (enumerator.MoveNext())
        {
            //int v; // In C# 5+ the variable is declared here.
            v = enumerator.Current;
            funcs.Add(() => v);
        }
    }

    foreach (var f in funcs)
        Console.WriteLine(f());
}

这将输出您的预期输出。