【问题标题】:Reproducing the "close over the variable of a foreach" gotcha重现“关闭 foreach 的变量”问题
【发布时间】:2015-12-16 17:56:03
【问题描述】:

使用 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 循环不会更改,更改不会“向后移植”到以前的 C# 版本。因此,您在使用此成语时应继续小心。

我做错了什么?

【问题讨论】:

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


    【解决方案1】:

    Scott 的回答是正确的,但可以使用一些额外的说明。

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

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

    进行此切换的原因是,开发团队中的一个人可以“试用”新版本的编译器,以确保他们的代码仍然有效,但在签入之前知道他们没有意外使用他们队友的编译器会阻塞的语言特性。因此,如果您将语言版本设置为 3.0,那么“动态”将不起作用,(因为它是在 C# 4.0 中添加的)但它仍然是您安装的编译器的任何版本。

    正如 Scott 指出的那样,如果您想使用旧编译器,您必须在您的机器上的某个地方实际找到旧编译器的副本并明确使用它。

    请参阅http://ericlippert.com/2013/04/04/what-does-the-langversion-switch-do/,了解有关此开关的作用和不作用的更多示例。

    【讨论】:

    • 代码何时会真正中断?我猜代码是否依赖于旧编译器中的错误,然后修复,或者新编译器是否引入了错误。还有其他可能有用的场景吗?
    • @xehpuk:我不确定我是否理解你的问题。毫无疑问,无论 langversion 开关的值如何,依赖于固定编译器错误的代码都会中断。编译器团队在修复错误时非常努力地保持向后兼容性,但这并不总是可行的。
    • 你是对的,它仍然是同一个编译器。所以用例基本上是一个项目成员拥有更新版本的编译器,但不能轻易使用旧版本(因为例如他/她正在使用更新版本的 VS,无论出于何种原因都没有该选项)。跨度>
    【解决方案2】:

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

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

    编辑: 如果您真的想重新创建行为,您需要编写自己的手动 for-each 循环。

    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());
    }
    

    这将输出您的预期输出。

    【讨论】:

    • 您不必为玩具示例安装旧版本的 VS。您可以通过从 C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe 手动运行 csc 来完成
    猜你喜欢
    • 1970-01-01
    • 2023-03-21
    • 1970-01-01
    • 2013-03-26
    • 2014-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-23
    相关资源
    最近更新 更多