C#:IEnumerable <t>。在某些情况下,选择()效率低下?

时间:2016-04-26 21:38:11

标签: c# linq

我最近了解到.NET的LINQ实现创建的对象对于特定的枚举类型效率低下。

看看这段代码:

public class DummyCollection : ICollection<int>
{
        public IEnumerator<int> GetEnumerator()
        {
            throw new Exception();
        }
        public int Count
        {
            get
            {
                return 10;
            }
        }
    //some more interface methods
}

基本上,DummyCollection的实例大小为10,但如果实际枚举则抛出异常。

现在在这里:

var d = new DummyCollection();
Console.WriteLine(d.Count());

打印出10没有错误,但是这段代码:

var l = d.Select(a=> a);
Console.WriteLine(l.Count());

抛出异常,尽管说l的大小也是10也是微不足道的(因为Select提供了1对1的映射)。这基本上意味着,当检查Ienumerable的长度时,输入可能是Select-wrapped Collection,从而将计算时间从O(1)扩展到惊人的O(n)(可能更糟,如果选择功能特别麻烦。)

我知道你在请求LINQ的泛型时会牺牲效率,但这似乎是一个很难解决的问题。我在网上查了一下,无法找到解决这个问题的人。有没有办法绕过这个缺点?有人在调查这个吗?有人修这个吗?这只是一个边缘情况,这不是一个大问题吗?任何见解都表示赞赏。

2 个答案:

答案 0 :(得分:6)

您可以看到Count()扩展方法的实施方式here。基本上是这样的:

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) throw Error.ArgumentNull("source");

    ICollection<TSource> collectionoft = source as ICollection<TSource>;

    if (collectionoft != null) return collectionoft.Count;

    ICollection collection = source as ICollection;

    if (collection != null) return collection.Count;

    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator()) {
        checked {
            while (e.MoveNext()) count++;
        }
    }
    return count;
}

正如您所看到的,方法检查首先是source类型为ICollection<TSource>ICollection,如果是这种情况,那么就不需要迭代计算元素,只需返回Count属性。

在您的第一种情况下,Count属性被称为返回10,并且永远不会调用GetEnumerator()方法。

当您使用Select()方法时,您要将该集合包装到另一种不是ICollection的类型中(在上面的链接中,您还可以看到Select()实现)因此迭代是必要的。

在第二种情况下,当您致电Count()时,系统会调用您的GetEnumerator()方法并抛出异常。

答案 1 :(得分:2)

IEnumerable<T>没有Count的概念。这存在于实现中,(除了这里和那里的奇怪捷径之外)在LINQ to Objects中没有任何作用。如果您使用IEnumerable<T>投影ICollection<T>的实施(例如Select),那么唯一真正的保证是输出将为IEnumerable<T> ...没有Count

LINQ应被视为处理项目序列,一次一个,只有当前和下一个项目(或序列结束)的概念。了解项目数量是一项(可能)成本高昂的操作,需要迭代所有被计算的项目,而不是在少数优化案例中。

鉴于LINQ 依赖 进行迭代而不是索引和计数意味着当您尝试迭代它时出现错误的IEnumerable将需要一些超级奇怪的特殊套管飞。对我来说,这不是一个非常有用的用例。