让我们从一些要查询的源数据开始:
int[] someData = { 1, 2 };
运行以下代码后,事情就像我期望的那样:a
包含2个元素,这些元素可归结为1
,2
从someData
拉出。
List<IEnumerable<int>> a = new List<IEnumerable<int>>();
a.Add(someData.Where(n => n == 1));
a.Add(someData.Where(n => n == 2));
但这个下一个代码只在循环中执行完全相同的操作,但不能按预期工作。当此代码完成时,b
包含2个元素但它们都相同 - 指向2
。在第二个循环中,它修改b
的第一个元素。
List<IEnumerable<int>> b = new List<IEnumerable<int>>();
for (int i = 1; i <= 2; ++i)
{
b.Add(someData.Where(n => n == i));
}
为什么会发生这种情况?如何使循环版本的行为与第一个版本相同?
答案 0 :(得分:7)
Jon Skeet有一个很好的答案here
您需要将i
分配给temp
变量,并在Linq查询中使用该变量
List<IEnumerable<int>> b = new List<IEnumerable<int>>();
for (int i = 1; i <= 2; ++i)
{
int temp = i;
b.Add(someData.Where(n => n == temp));
}
答案 1 :(得分:4)
你的问题是懒惰的评价。您可以将Enumerable添加到代表b
的{{1}}。每当您查看someData.Where(n => n == i)
的元素b
时,就会对此进行评估。
您希望通过在其上调用i
或ToArray()
来显示可枚举项。
ToList()
或者,您可以缩小捕获变量的范围:
for (int i = 1; i <= 2; ++i)
{
b.Add(someData.Where(n => n == i).ToArray());
}
然后你仍然有懒惰的评估枚举(当你修改for (int i = 1; i <= 2; ++i)
{
int localI=i;
b.Add(someData.Where(n => n == localI));
}
时显示),但每个都有不同的someData
。
答案 2 :(得分:3)
对于循环中where子句的每个声明,都有一个单独的Func&lt; int,bool&gt;正在传递给它的实例。由于i的值被传递给与Func&lt; int,bool&gt;的每个实例相关联的lambda函数。 (Func&lt; int,bool&gt;本质上是委托),i是在Func&lt; int,bool&gt;的每个实例之间共享的捕获变量。
换句话说,即使在循环范围之外,我必须“保持活着”,以便在任何Func&lt; int,bool&gt;的实例时评估其值。被调用。通常,这不会是一个问题,除非在必要之前不会发生调用(即需要枚举查询的结果,委托实例被传递给。例如,foreach循环:仅然后调用委托以便为迭代目的确定查询结果。)
这意味着循环中声明的LINQ查询在for循环结束后的某个时间才会真正生成,这意味着我将设置为2的值。结果,你实际上在做像这样的东西:
b.Add(someData.Where(n => n == 2));
b.Add(someData.Where(n => n == 2));
为防止这种情况发生,对于循环中的每次迭代,您需要声明整数类型的单独实例并使其等效于i。将此传递给迭代中声明的lambda函数以及Func&lt; int,bool&gt;的每个实例。将有一个单独的捕获变量,其值在每次后续迭代后都不会被修改。例如:
for (int i = 1; i <= 2; ++i)
{
int j = i;
b.Add(someData.Where(n => n == j));
}
答案 3 :(得分:1)
这是捕获的loop-var问题的略微隐藏的变化。
你可以这样解决:
List<IEnumerable<int>> b = new List<IEnumerable<int>>();
for (int i = 1; i <= 2; ++i)
{
int j = i; // essential
b.Add(someData.Where(n => n == j));
}
但更直观的是
List<IEnumerable<int>> b = new List<IEnumerable<int>>();
for (int i = 1; i <= 2; ++i)
{
b.Add(someData.Where(n => n == i).ToList());
}
原始代码中发生的事情是捕获变量i
(关闭)并将引用存储在lambdas中。结果是IEnumerable
s,menas延期执行。 i
的值仅在显示/检查结果时获取,到那时它是2
。