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

时间:2011-05-27 15:04:07

标签: 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; }
}

基本上,“A”应该有数据[A1,A2]和“B”应该有数据[B1,B2]。

然而,当你运行这个“A”获得数据[B1,B2]和B.一样。为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; }
}

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

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

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

1 个答案:

答案 0 :(得分: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});
}

有关此问题的详细信息,请参阅Eric Lippert的博文“关闭循环变量被视为有害”:part onepart two

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