我有一个关于在循环中定义的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…;^)
共享的不是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中的有效但不正确的代码,这是个危险的位置