为什么WhereSelectArrayIterator没有实现ICollection?

Ale*_*man 1 c# performance list icollection toarray

在通过Reflector 查看System.Linq.Enumerable时,我注意到用于SelectWhere扩展方法的默认迭代器- WhereSelectArrayIterator - 没有实现ICollection接口.如果我正确读取代码,这会导致一些其他扩展方法,如Count()ToList()执行较慢:

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
    // code above snipped
    if (source is List<TSource>)
    {
        return new WhereSelectListIterator<TSource, TResult>((List<TSource>) source, null, selector);
    }
    // code below snipped
}

private class WhereSelectListIterator<TSource, TResult> : Enumerable.Iterator<TResult>
{
    // Fields
    private List<TSource> source; // class has access to List source so can implement ICollection
    // code below snipped
}


public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
{
public List(IEnumerable<T> collection)
{
    ICollection<T> is2 = collection as ICollection<T>;
    if (is2 != null)
    {
        int count = is2.Count;
        this._items = new T[count];
        is2.CopyTo(this._items, 0); // FAST
        this._size = count;
    }
    else
    {
        this._size = 0;
        this._items = new T[4];
        using (IEnumerator<T> enumerator = collection.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                this.Add(enumerator.Current);  // SLOW, CAUSES ARRAY EXPANSION
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

}

我用结果测试了这一点,证实了我的怀疑:

ICollection:2388.5222 ms

IEnumerable:3308.3382 ms

这是测试代码:

    // prepare source
    var n = 10000;
    var source = new List<int>(n);
    for (int i = 0; i < n; i++) source.Add(i);

    // Test List creation using ICollection
    var startTime = DateTime.Now;
    for (int i = 0; i < n; i++)
    {
        foreach(int l in source.Select(k => k)); // itterate to make comparison fair
        new List<int>(source);
    }
    var finishTime = DateTime.Now;
    Response.Write("ICollection: " + (finishTime - startTime).TotalMilliseconds + " ms <br />");

    // Test List creation using IEnumerable
    startTime = DateTime.Now;
    for (int i = 0; i < n; i++) new List<int>(source.Select(k => k));
    finishTime = DateTime.Now;
    Response.Write("IEnumerable: " + (finishTime - startTime).TotalMilliseconds + " ms");
Run Code Online (Sandbox Code Playgroud)

我错过了什么或将在未来版本的框架中修复?

谢谢你的想法.

Str*_*ior 5

LINQ to Objects使用一些技巧来优化某些操作.例如,如果将两个.Where语句链接在一起,则谓词将合并为一个WhereArrayIterator,因此之前的谓词可以被垃圾收集.同样,a Where后跟a Select将创建一个WhereSelectArrayIterator,将组合谓词作为参数传递,以便原始文件WhereArrayiterator可以被垃圾收集.因此,WhereSelectArrayIterator负责不仅是跟踪selector,而且还合并predicate,它可能会或可能不会根据.

source字段仅跟踪给定的初始列表.由于谓词,迭代结果不会总是具有相同数量的项目source.由于LINQ意在惰性计算,它不应该评估sourcepredicate时间提前刚刚以便它能够潜在地节省时间,如果有人结束了通话.Count().这将导致同样多的性能损失作为调用.ToList()手动就可以了,如果用户通过多个运行它WhereSelect条款,你最终不必要构建多个列表.

LINQ to Objects是否可以重构以创建SelectArrayIteratorSelect直接在数组上调用时使用的对象?当然.它会提高性能吗?一点点.费用是多少?减少代码重用意味着需要额外的代码来维护和测试前进.

因此,我们得到绝大多数"为什么语言/平台X没有功能Y"问题的关键:每个功能和优化都有一些与之相关的成本,甚至微软也没有无限的资源.就像其他所有公司一样,他们会做出判断调用,以确定执行代码运行的频率Select,然后调用.ToList()它,以及是否更快地运行该代码是值得编写和维护LINQ中的另一个类包.