循环中定义的Linq表达式的范围 - 问题:关闭循环变量

Rik*_*iko 2 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 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; }
}
Run Code Online (Sandbox Code Playgroud)

基本上,"A"应该有数据[A1,A2]和"B"应该有数据[B1,B2].

但是,当您运行此"A"获取数据[B1,B2]时,BIe也会为所有Result实例计算最后一个表达式.

鉴于我在循环中声明了"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; }
}
Run Code Online (Sandbox Code Playgroud)

如果我在迭代中强制进行评估,我会得到所需的结果,这不是我的问题.

我问为什么"myData"似乎在迭代中共享,因为我在一次迭代的范围内声明了它?

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

Jon*_*eet 5

它不是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});
}
Run Code Online (Sandbox Code Playgroud)

有关此问题的更多信息,请参阅Eric Lippert的博客文章"关闭循环变量被认为有害":第一部分,第二部分.

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