c# – 哪些情况下是IEnumerable.Count优化?

使用 reflector我注意到, System.Linq.Enumerable.Count方法有一个条件来优化IE的可能性< T>传递实际上是ICollection< T>.如果转换成功,Count方法不需要遍历每个元素,而可以调用ICollection的Count方法.

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

我感兴趣的是,当IEnumerable< T>是通过ICollection的Select语句的结果,但是基于反映的代码,这种情况未被优化,并且需要通过所有元素进行迭代.

你从反射镜得出同样的结论吗?什么可能是缺乏这种优化背后的原因?我似乎在这个共同的操作中浪费了很多时间.规范是否要求每个元素进行评估,即使可以确定Count而不执行?

Select选择的结果并不重要.计数总是相当于原始集合的计数,因此它可以通过从Select中的特定对象返回直接检索,该对象可用于对Count方法进行短路评估.

不可能优化对具有确定计数(例如List< T>)的某个Select Select的返回值对Count()方法进行优化的原因在于它可以改变程序的含义.

传递给Select方法的选择器功能被允许具有副作用,其副作用需要以预定顺序进行确定性的发生.

承担:

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

该文档需要打印此代码

1
2
3

即使计数从一开始就是真正知道的,可以进行优化,优化会改变程序的行为.这就是为什么你不能避免收集的枚举.这正是纯功能语言中编译器优化更容易的原因之一.

更新:显然,不清楚实现“选择和计数”是完全可能的,以便选择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 ...
}

这仍然会懒惰地评估Count.如果您更改底层集合,则计数将被更改,并且序列不被缓存.唯一的区别将不是在选择器委托中执行副作用.

相关文章
相关标签/搜索