如何在循环中使用linq
本文关键字:linq 循环 | 更新日期: 2023-09-27 18:13:06
让我们从一些要查询的源数据开始:
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));
}
为什么会发生这种情况,我如何使循环版本表现得像第一个版本?
Jon Skeet有一个很好的答案
您需要将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));
}
你的问题是懒惰的计算。向b
添加一个代表someData.Where(n => n == i)
的Enumerable。当您查看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
时显示),但是每个枚举都有不同的i
。
对于循环中的where子句的每个声明,都有一个单独的Func<int,>被传递给它的实例。由于i的值被传递给与Func<int实例相关联的lambda函数,因此bool>(Func<int,>(本质上是一个委托),它是一个捕获的变量,在Func<int,>的每个实例之间共享。
换句话说,即使在循环范围之外,i也必须"保持活动",以便在任何Func<int,>被调用。通常情况下,这不会是一个问题,除非调用在必要时才会发生(例如,需要枚举传递给委托实例的查询结果)。foreach循环就是一个例子:只有在这种情况下,才会调用委托,以便为迭代的目的确定查询的结果。
这意味着在循环中声明的LINQ查询直到for循环结束后的一段时间才会真正执行,这意味着i将被设置为2。因此,您实际上是在做这样的事情:
b.Add(someData.Where(n => n == 2));
b.Add(someData.Where(n => n == 2));
为了防止这种情况发生,对于循环中的每次迭代,需要声明一个单独的整数类型实例,并使其等同于i。将其传递给迭代中声明的lambda函数和Func<int,>将有一个单独的捕获变量,其值在每次后续迭代后都不会被修改。例如:
for (int i = 1; i <= 2; ++i)
{
int j = i;
b.Add(someData.Where(n => n == j));
}
这是捕获的循环变量问题的一个稍微隐蔽的变化。
你可以这样求解:
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,表示延迟执行。i
的值仅在显示/检查结果时获取,到那时它是2
。