在哪些情况下IEnumerable <T> .Count优化?

sho*_*tsy 14 c# optimization ienumerable count

使用反射器我注意到该System.Linq.Enumerable.Count方法有一个条件来优化它,因为IEnumerable<T>传递实际上是一个ICollection<T>.如果转换成功,则Count方法不需要迭代每个元素,但可以调用ICollection的Count方法.

基于此,我开始认为IEnumerable<T>可以像集合的只读视图一样使用,而不会出现我最初基于API的性能损失.IEnumerable<T>

我感兴趣的是,CountIEnumerable<T>a是一个Select语句的结果时,仍然保持优化ICollection,但是基于反映的代码,这种情况没有被优化,并且需要遍历所有元素.

你从反射器得出相同的结论吗?缺少这种优化背后的原因是什么?我似乎在这个常见的操作中浪费了很多时间.即使可以在不这样做的情况下确定Count ,规范是否要求评估每个元素?

Meh*_*ari 12

Select懒惰评估结果并不重要.的Count,因此它可以有一定直接通过返回一个特定对象从检索总是等于原始集合的计数Select可用于向的短路评价Count方法.

无法从具有确定计数(如a )的事物中优化对Count()方法的返回值的方法评估的原因是它可以改变程序的含义.SelectList<T>

selector传递给Select方法的函数允许具有副作用,并且需要以预定顺序确定性地发生其副作用.

假设:

new[]{1,2,3}.Select(i => { Console.WriteLine(i); return 0; }).Count();
Run Code Online (Sandbox Code Playgroud)

该文档要求打印此代码

1
2
3

尽管从一开始就知道计数并且可以进行优化,但优化会改变程序的行为.这就是为什么你无论如何都无法避免集合的枚举.这正是纯函数式语言中编译器优化更容易的原因之一.


更新:显然,目前还不清楚,这是完全可以实现Select,并CountSelectS ON ICollection<T>仍然会懒洋洋地评价,但Count()将在O(1)无需枚举集合进行评估.我将在不改变任何方法的界面的情况下这样做.类似的事情已经完成ICollection<T>:

private interface IDirectlyCountable {
    int Count {get;}
}
private class SelectICollectionIterator<TSource,TResult> : IEnumerable<T>, IDirectlyCountable {
     ICollection<TSource> sequence;
     Func<TSource,TResult> selector;
     public SelectICollectionIterator(ICollection<TSource> source, Func<TSource,TResult> selector) {
         this.sequence = source;
         this.selector = selector;
     }
     public int Count { get { return sequence.Count; } }
     // ... GetEnumerator ... 
}
public static IEnumerable<TResult> Select<TSource,TResult>(this IEnumerable<TSource> source, Func<TSource,TResult> selector) {
    // ... error handling omitted for brevity ...
    if (source is ICollection<TSource>)
       return new SelectICollectionIterator<TSource,TResult>((ICollection<TSource>)source, selector);
    // ... rest of the method ...
}
public static int Count<T>(this IEnumerable<T> source) {
    // ...
    ICollection<T> collection = source as ICollection<T>;
    if (collection != null) return collection.Count;
    IDirectlyCountable countableSequence = source as IDirectlyCountable;
    if (countableSequence != null) return countableSequence.Count;
    // ... enumerate and count the sequence ...
}
Run Code Online (Sandbox Code Playgroud)

这仍然会对Count懒惰进行评估.如果更改基础集合,则计数将更改,并且不会缓存序列.唯一的区别是不会在selector代表中做副作用.