我有一个关于在循环中定义的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 ......; ^)
答案 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 one,part two。
这是语言设计方式的一个不幸的神器,但现在改变它将是一个坏主意IMO。虽然任何改变行为的代码基本上都会事先被破坏,但这意味着C#6中的正确代码在C#5中是有效但不正确的代码,这是一个危险的位置。