2012-11-06 96 views
20

我有一个项目列表和一个LINQ查询。现在,随着LINQ的延期执行,后续的foreach循环只会执行一次查询还是循环中的每一次执行?foreach只执行一次查询吗?

考虑这个例子(来自Introduction to LINQ Queries (C#), on MSDN两者)​​

// The Three Parts of a LINQ Query: 
    // 1. Data source. 
    int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; 

    // 2. Query creation. 
    // numQuery is an IEnumerable<int> 
    var numQuery = 
     from num in numbers 
     where (num % 2) == 0 
     select num; 

    // 3. Query execution. 
    foreach (int num in numQuery) 
    { 
     Console.Write("{0,1} ", num); 
    } 

或者,换句话说,会有任何区别,如果我有

foreach (int num in numQuery.ToList()) 

而且,那就没关系,如果底层数据不在数组中,而是在数据库中?

回答

15

现在,随着LINQ的延期执行,后续的foreach循环只会执行一次查询还是每循环执行一次查询?

是的,一次为循环。实际上,它可能少于一次地执行查询 - 您可以中止循环部分,并且不会对任何剩余项目执行测试。

或者,换句话说,会不会有什么区别,如果我有:

foreach (int num in numQuery.ToList()) 

两点区别:

  1. 在上述情况下,ToList()浪费时间和内存,因为它首先与最初的foreach执行相同的操作,从中建立一个列表,然后从那个列表中建立一个列表foreach。根据结果​​的大小,差异将介于微不足道和阻止代码运行之间。

  2. 但是,如果你要反复相同的结果做foreach,否则反复使用的情况下,再而foreach只运行查询一次,下foreach再次运行它。如果查询很昂贵,那么ToList()方法(并存储该列表)可以是一个巨大的节约。

+0

是的。正如上面的编辑所解释的,也许甚至不到整个查询的执行。 –

+0

感谢您详细解释我! – Marcel

8

不,它没有区别。 in表达式被评估一次。更具体而言,foreach构造调用in表达式上的GetEnumerator()方法,并重复调用MoveNext()并访问Current属性以遍历IEnumerable

OTOH,呼叫ToList()是多余的。你不应该打扰它。

如果输入的是一个数据库,情况稍有不同,因为LINQ输出IQueryable,但我敢肯定,foreach仍然将其视为IEnumerable(其中IQueryable继承)。

+0

...这就是为什么在尝试更改集合的foreach通过时引发异常。 – Alex

+0

@Alex或严格来说,为什么它可能被抛出。 “foreach”的规则并不保证允许修改,但集合可以自由地允许它,有时必须(如果它们必须禁止来自其他线程的同时更改,则为并发使用而设计的那些会受到阻碍)。 –

2

正如所写,循环的每次迭代将完成尽可能多的工作,以获取下一个结果。所以答案在技术上是“以上都不是”。该查询将“分段”执行。

如果使用ToList()或任何其他实现方法(ToArray()等),那么查询将被一次当场和后续操作(如遍历结果)评估将只是一个“哑”名单上的工作。

如果numbers是一个IQueryable而不是IEnumerable - 因为它很可能是数据库中的场景 - 那么,上述仍接近真相虽然不是一个完全准确的描述。特别是,在实现结果的第一次尝试中,可查询提供程序将与数据库进行通信并生成结果集;那么,这个结果集中的行将在每次迭代中被拉动。

+0

感谢您解释数据库部分。 – Marcel

1

当它被列举的LINQ查询将被执行(无论是作为一个.ToList()调用的结果或做foreach了结果。

如果您枚举LINQ查询的结果两次,两次都会导致它查询数据源(在你的例子中,枚举集合),因为它本身只返回IEnumerable。但是,根据linq查询,它可能并不总是枚举整个集合(例如.Any().Single()将会如果存在.Where(),则停止在第一个对象或第一个匹配对象上)。

一个LINQ提供程序的实现细节可能有所不同,因此通常的行为时,数据源是一个数据库调用.ToList()马上到cache查询&的结果也保证了查询(在EF的情况下, L2S或NHibernate)是,然后执行一次,然后,而不是在代码后面的某个点枚举集合,并且如果多次枚举结果时阻止多次执行查询。