C# 在有争议的循环中定义的Linq表达式的范围:结束循环变量

C# 在有争议的循环中定义的Linq表达式的范围:结束循环变量,c#,linq,linq-to-objects,C#,Linq,Linq To Objects,我有一个关于循环中定义的Linq表达式的范围问题。以下LinqPad C程序演示了该行为: void Main() { string[] data=new string[] {"A1", "B1", "A2", "B2" }; string[] keys=new string[] {"A", "B" }; List<Result> results=new List<Result>(); foreach (string key in ke

我有一个关于循环中定义的Linq表达式的范围问题。以下LinqPad C程序演示了该行为:

void Main()
{
    string[] data=new string[] {"A1", "B1", "A2", "B2" };
    string[] keys=new string[] {"A", "B" };

    List<Result> results=new List<Result>();

    foreach (string key in keys) {
        IEnumerable<string> myData=data.Where (x => x.StartsWith(key));     
        results.Add(new Result() { Key=key, Data=myData});          
    }   
    results.Dump();
}

// Define other methods and classes here
class Result {
    public string Key { get; set; }
    public IEnumerable<string> Data { get; set; }
}
基本上,A应该有数据[A1,A2],B应该有数据[B1,B2]

但是,当您运行此操作时,A会像B一样获取数据[B1,B2]。也就是说,最后一个表达式将针对结果的所有实例进行计算

既然我在循环内声明了myData,为什么它的行为就好像我在循环外声明它一样?如果我这样做的话,它的表现就像我期望的那样:

void Main()
{
    string[] data=new string[] {"A1", "B1", "A2", "B2" };
    string[] keys=new string[] {"A", "B" };

    List<Result> results=new List<Result>();

    IEnumerable<string> myData;                 
    foreach (string key in keys) {
        myData=data.Where (x => x.StartsWith(key));     
        results.Add(new Result() { Key=key, Data=myData});          
    }   
    results.Dump();
}

// Define other methods and classes here
class Result {
    public string Key { get; set; }
    public IEnumerable<string> Data { get; set; }
}
如果我在迭代中强制求值,我会得到期望的结果,这不是我的问题

我想问的是,既然我在单个迭代的范围内声明了myData,为什么myData看起来会在迭代之间共享

有人叫乔恩·斯基特^

共享的不是myData,而是关键。由于myData中的值是惰性计算的,因此它们取决于key的当前值

这是因为迭代变量的作用域是整个循环,而不是循环的每个迭代。这里有一个键变量,它的值会发生变化,而lambda表达式会捕获该变量

正确的解决方法是将迭代变量复制到循环中的变量中:

foreach (string key in keys) {
    String keyCopy = key;
    IEnumerable<string> myData = data.Where (x => x.StartsWith(keyCopy));     
    results.Add(new Result() { Key = key, Data = myData});
}
有关此问题的更多信息,请参阅Eric Lippert的博客文章Closing over the loop变量被认为是有害的:


这是语言设计方式的一个不幸产物,但在我看来,现在改变它将是一个坏主意。虽然任何改变行为的代码基本上都是事先被破坏的,但这意味着在C 6中正确的代码是有效的,而在C 5中则是不正确的代码,这是一个危险的处境。

ReSharper实际上会抱怨这个问题,并帮你抓住它。我相信这是指试图使用修改后的闭包时出现的问题。仅供参考,很有可能在下一版本中会发生更改。我们已经与CLR兼容性委员会进行了谈判,我们认为我们可以在不太可能破坏人身安全的情况下实现这一改变。我同意在C 4中重新编译C 5代码的风险是一个很重要的问题,但我认为解决这个问题的好处是相当大的。@埃里克:谢谢你的提醒。我同意这样做的好处是巨大的。一整班可以避免的问题。。。虽然有一些方法可以选择性地告诉开发人员他们依赖于此,但这可能是一种有点奇怪的标记。在两个帐户中使您的电子邮件地址相同,然后再次标记。