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

ben*_*iii 1 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
}
Run Code Online (Sandbox Code Playgroud)

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

现在这里:

var d = new DummyCollection();
Console.WriteLine(d.Count());
Run Code Online (Sandbox Code Playgroud)

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

var l = d.Select(a=> a);
Console.WriteLine(l.Count());
Run Code Online (Sandbox Code Playgroud)

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

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

Art*_*aca 6

您可以在此处查看Count()扩展方法的实现方式.基本上是这样的:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

在第二个情况下,当你调用Count()你的GetEnumerator()方法被调用,并抛出异常.

  • @bentheiii当您的下游用户想要在您的包装集合上使用`ICollection <T> .Add`时会发生什么?你需要提供反投影方法吗?LINQ并不是关于集合,计数和索引,但你有机会将物化集合与扩展方法混合在一起,例如`ToList`,`ToArray`,`ToDictionary`和`ToLookup`.试图将所有这些内容转变为核心LINQ将使其变得笨拙(想想需要发生的所有不同类型的特征检测),并且很可能是不可行的. (2认同)