循环中定义的Linq表达式的作用域-问题:结束循环变量

本文关键字:循环 问题 变量 结束 表达式 定义 Linq 作用域 | 更新日期: 2023-09-27 17:57:45

我有一个关于在循环中定义的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"得到数据[B1,B2]时,就像B一样。也就是说,最后一个表达式是为Result的所有实例计算的。

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

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",为什么它似乎在迭代之间共享?

有人打电话给Jon Skeet…;^)

循环中定义的Linq表达式的作用域-问题:结束循环变量

共享的不是myData,而是key。由于myData中的值是延迟求值的,因此它们取决于key当前值。

它的行为是这样的,因为迭代变量的范围是整个循环,而不是循环的每个迭代。您有一个单个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的博客文章"关闭被认为有害的循环变量":第一部分,第二部分。

按照语言的设计方式,这是一个不幸的产物,但现在更改它将是一个坏主意。虽然任何改变行为的代码基本上都会事先被破坏,但这意味着(比如)C#6中的正确代码将是C#5中的有效但不正确的代码,这是个危险的位置