如何在循环中使用linq

本文关键字:linq 循环 | 更新日期: 2023-09-27 18:13:06

让我们从一些要查询的源数据开始:

int[] someData = { 1, 2 };

运行以下代码后,事情如我预期的那样工作:a包含2个元素,它们归结为12,从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));
}

为什么会发生这种情况,我如何使循环版本表现得像第一个版本?

如何在循环中使用linq

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