Linq表现:(ElementAt,Count)vs(foreach)

我使用(ElementAt,Count)和(foreach)作为Linq查询的结果迭代IEnumerable。 令我惊讶的是,性能差异是25-30倍! 这是为什么?

IEnumerable result = ... simple Linq query that joins two tables ... returns about 600 items double total = 0; // Method 1: iterate with Count and ElementAt for( int i = 0; i < result.Count(); i++ ) { total += result.ElementAt(i); } // Method 2: iterate with foreach foreach( double value in result ) { total += value; } 

ElementAt()方法是O(n),除非IEnumerable表示的实际具体类优化它。 这意味着每次调用它时,它必须循​​环遍历整个Enumerable以在n处找到元素。 更不用说因为你在for循环的条件部分中有i < result.Count() ,所以每次都要循环遍历整个枚举以获得该计数。

第二种方式,你只需要循环一次result

因为每次调用它时, ElementAt都会遍历IEnumerableIEnumerable s未编入索引,因此必须使用GetEnumerator()实现ElementAt

为什么不呢

 total = result.Sum(); 

因为每个调用遍历列表。 简单的解决方案是在迭代之前调用.ToList(),更好的解决方案是停止迭代。

 var theList = result.ToList(); for( int i = 0; i < result.Count; i++ ) { total += result[i]; } 

更好的方案:

 total = result.Sum(); 

性能差异是由于IEnumerable不允许您通过索引获取,因此每次在第一个循环中调用ElementAt ,它必须遍历每个项目,直到它到达您请求的元素。

如果我冒险猜测,result.Count()调用是非延迟的,实际上是命中数据库,而foreach没有。 如果您翻转订单,可能会得到相反的结果。 你也可以做total = result.Sum();

第一个可能相当于:

 double total = 0; int i = 0; while(true) { int max = /*Database call to obtain COUNT(*) of join*/ if(max > i) break; int j = 0; foreach(double value in result) { if(j++ == i) { total += value; break; } } ++i } 

或者甚至可以相当于:

 double total = 0; int i = 0; while(true) { int max = 0; foreach(double value in result) ++max; if(max > i) break; int j = 0; foreach(double value in result) { if(j++ == i) { total += value; break; } } ++i } 

或者它甚至可以重新查询每次它在上面的代码中出现时获得result

另一方面, Count() 可以通过一个属性访问获得,而ElementAt()可以是O(1),如果它们支持允许这种优化的结构,并且这样的优化确实可用(例如它适用于List )。

这就是了解延期执行! 当多次执行查询时,运行时间会急剧增加。 LINQ可以更快,但您确实需要根据您将如何使用查询结果做出选择。

请查看这篇文章http://allthingscs.blogspot.com/2011/03/linq-performance.html 。 它分析了这个问题。