IEnumerable Count()和Length的区别

78

IEnumerable Count()Length 有什么关键区别?

3个回答

116
通过调用`IEnumerable`上的Count,我假设您指的是`System.Linq.Enumerable`上的扩展方法Count。Length不是`IEnumerable`上的方法,而是.NET中数组类型(如int[])上的属性。
区别在于性能。Length属性保证是O(1)的操作。Count扩展方法的复杂度取决于对象的运行时类型。它会尝试将其转换为几种支持O(1)长度查找的类型,例如通过Count属性支持的`ICollection`。如果没有可用的类型,则会枚举所有项并计数,这具有O(N)的复杂度。
例如
int[] list = CreateSomeList();
Console.WriteLine(list.Length);  // O(1)
IEnumerable<int> e1 = list;
Console.WriteLine(e1.Count()); // O(1)
IEnumerable<int> e2 = list.Where(x => x <> 42);
Console.WriteLine(e2.Count()); // O(N)

e2 被实现为 C# 迭代器,不支持 O(1) 计数,因此方法 Count 必须枚举整个集合来确定其长度。

8
List<T> 没有 Length 属性,而是有一个名为 Count 的属性。不过,数组有 Length 属性。Count 属性在 ICollectionICollection<T> 中定义(IList<T> 继承自这两个接口)。 - Jon Skeet
@JonSkeet 和 @Jared - 在解析一个包含5-10个元素的短string[]数组的情况下,你们建议使用Array.Length来提高性能吗? - one.beat.consumer
1
@one.beat.consumer:如果数组实现了ICollection<T>,那么使用Count()仍然是O(1),但它比直接使用Length要低效。如果您已经知道它是一个数组,我会使用Length而不是为了效率,而是因为我认为这更符合惯用法。同样,对于任何具有编译时类型ICollection<T>的内容,我会使用Count属性。当编译时表达式的类型为IEnumerable<T>时,即使我知道其背后实际上是一个数组,我也会调用Count() - Jon Skeet
@JonSkeet:谢谢。我(愚蠢地)假设System.Array是一个更基本的类(类似于object); 但在查看MSDN后,我发现它实现了几个“集合相关”的接口。我是一个Web程序员,所以“Count”一开始更有语义意义(就像HTML中的headerdiv),但我理解你的观点,因为它在编译时明确是一个string[],所以Length更有意义。 - one.beat.consumer
嗨,JaredPar,你能否解释一下,如果我在某个循环中更改列表,复杂度是否仍为O(1)?例如:while(list.Count() > 0)执行一些包含添加/移除操作的逻辑。 - Johnny_D

29

Jon Skeet的评论的小补充。

这是Count()扩展方法的源代码:

.NET 3:

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    ICollection<TSource> is2 = source as ICollection<TSource>;
    if (is2 != null)
    {
        return is2.Count;
    }
    int num = 0;
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            num++;
        }
    }
    return num;
}

.NET 4:

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    ICollection<TSource> is2 = source as ICollection<TSource>;
    if (is2 != null)
    {
        return is2.Count;
    }
    ICollection is3 = source as ICollection;
    if (is3 != null)
    {
        return is3.Count;
    }
    int num = 0;
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            num++;
        }
    }
    return num;
}

9
иҜ·жіЁж„ҸпјҢеңЁ.NET 4дёӯиҝҳжңүеҸҰдёҖдёӘеқ—з”ЁдәҺжЈҖжҹҘйқһжіӣеһӢзҡ„ICollectionзұ»еһӢпјҲеӣ дёәе®ғд№ҹе…·жңүCountеұһжҖ§пјүгҖӮ - Jon Skeet
有人知道该使用什么来获取此方法使用的“Error”类吗?我似乎在 MSDN 上找不到它,除了 JScript 文档之外。 - Moshe Katz

3
长度是一个固定的属性,例如单维数组或字符串。因此,永远不需要进行计数操作(多维数组的大小为所有维度相乘)。这里的O(1)操作意味着检索时间始终相同,无论有多少元素。与此相反,线性搜索是O(n)。
ICollections(例如List和List)上的Count属性可以更改,因此在添加/删除操作时必须更新它,或者在更改集合后请求Count时更新它。这取决于对象的实现方式。
LINQ的Count()方法基本上每次调用时都会迭代一次(除非对象是ICollection类型,然后会请求ICollection.Count属性)。
请注意,IEnumerables通常不是已定义的对象集合(如列表、数组、哈希表等),而是链接到后台操作的引用,只有在请求时才生成结果(称为延迟执行)。
通常,您会有一个类似SQL的LINQ语句(延迟执行的典型应用):
IEnumerable<Person> deptLeaders = 
   from p in persons
   join d in departments
      on p.ID equals d.LeaderID
   orderby p.LastName, p.FirstName
   select p;

然后,有这样的代码:
if (deptLeaders.Count() > 0)
{
   ReportNumberOfDeptLeaders(deptLeaders.Count());
   if (deptLeaders.Count() > 20)
      WarnTooManyDepartmentLeaders(deptLeaders.Count());
}

所以,当发出关于部门领导过多的警告时,.NET会四次遍历这些人员,将它们与部门领导进行对比,按姓名排序,然后计算结果对象的数量。
而且,这仅适用于人员和部门是预设值集合的情况,而不是查询本身。

我可以补充一下,.Count() > 0.Any() 是相同的。 - jedmao
2
@sfjedi: 我认为它们不同。当找到一个项目时,Any()会停止,而Count()会遍历全部。因此,在具有可能进行延迟执行的 IEnumerable 的情况下,应首选使用 Any() 进行空检查。 - Erik Hart
4
那么,使用.Any()难道不比使用.Count() > 0更有效吗?顺便说一句,Resharper总是会抱怨.Count() > 0。这就是我有把握提出这个问题的原因。 - jedmao
这个答案可以链接到官方文档。 - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接