哪种方法表现更佳:.Any() vs .Count() > 0?

713
System.Linq 命名空间中,我们现在可以扩展 IEnumerable 以拥有 Any()Count() 的扩展方法。
最近我被告知,如果我想检查一个集合是否包含一个或多个项,我应该使用 .Any() 扩展方法而不是 .Count() > 0 扩展方法,因为 .Count() 扩展方法必须遍历所有的项。
其次,一些集合有一个属性(而不是扩展方法),叫做 CountLength。与 .Any().Count() 相比,使用它们会更好吗?
是 / 否?

最好在可枚举对象上使用Any(),在集合上使用Count()。如果有人觉得写'(somecollection.Count > 0)'会导致混淆或可读性问题,最好将其编写为扩展方法并命名为Any()。这样每个人都满意,无论是性能方面还是可读性方面。因此,您的所有代码都将具有一致性,项目中的各个开发人员不必担心选择Count vs Any。 - Mahesh Bongani
3
你可能见过Count() > 0和Any()的区别,但你见过Distinct().Count() > 1和Distinct().Skip(1).Any()的区别吗?对于大量项目的情况下,后者明显更快,因为Count()需要遍历整个集合才能得到数量。Skip(1).Any()可以避免全枚举。在一个包含1000个长度为1的字符串数组中进行100k次检查,Count() > 1需要大约4000ms,而Skip(1).Any()只需要20ms。 - Triynko
11个回答

867

如果你在处理带有 .Length 或者 .Count 属性(如 ICollection<T>, IList<T>, List<T> 等)的数据时,使用 Count() 方法将是最快的选择,因为它不需要像 Any() 方法那样通过 GetEnumerator()/MoveNext()/Dispose() 这一序列来检查非空的 IEnumerable<T> 序列。

对于普通的 IEnumerable<T>Any() 方法会通常更快,因为它只需要查看一个迭代。然而,需要注意的是,Count() 的 LINQ-to-Objects 实现确实会检查 ICollection<T>(使用 .Count 作为优化),所以如果你的基础数据源是直接的列表/集合,差异并不会很大。不知道为什么它不使用非泛型的 ICollection...

当然,如果你已经使用 LINQ 进行过筛选等操作(例如 Where),那么你将拥有一个基于迭代块的序列,这时候使用 ICollection<T> 优化就毫无用处了。

总之,在处理 IEnumerable<T> 时,建议使用 Any() 方法。;-p


15
Marc说:ICollection<T> 实际上并不是从ICollection派生的。我也很惊讶,但Reflector不会撒谎。 - Bryan Watts
8
任何()方法的实现会检查ICollection接口并在之后检查Count属性吗? - derigel
376
我认为大多数情况下使用Any()还有另一个原因。它能够准确传达开发者的意图。如果你只是想知道是否有某些东西,而不关心数量,那么使用somecollection.Any()比使用somecollection.Count > 0更简单、更清晰。 - Torben Junker Kjær
33
你认识多少开发者真正被(somecollection.Count > 0)这个语句搞糊涂了?在引入LINQ的.Any()方法之前,我们所有的代码都很难理解吗? - CraigTP
38
我认为 someCollection.Count > 0someCollection.Any() 一样清晰,而且具有更好的性能和不需要使用 LINQ 的优点。当然,这只是一个非常简单的情况,其他使用 LINQ 操作符的构造将比等效的非 LINQ 选项更清晰地表达开发人员的意图。 - CraigTP
显示剩余16条评论

70

注意:我写这个答案时,Entity Framework 4才是最新版本。 这个答案的重点不是琐碎的.Any().Count()性能测试的比较。而是要表明EF远非完美。新版本更好...但如果您有一部分代码很慢并且使用了EF,请使用直接的T-SQL进行测试并比较性能,而不是依赖假设(即.Any()总比.Count()>0快)。


虽然我同意大多数得到赞的答案和评论,尤其是关于AnyCount()>0更好地表示开发人员意图的观点,但我曾经在SQL Server(EntityFramework 4)上遇到过CountAny快一个数量级的情况。

这里是一个带有Any的查询,它在大约200,000条记录上引发了超时异常:

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && !a.NewsletterLogs.Any(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr)
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

Count 版本在毫秒级别内执行:

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && a.NewsletterLogs.Count(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr) == 0
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();
我需要找到一种方法来查看两个LINQ语句生成的确切SQL - 但在某些情况下,CountAny之间存在巨大的性能差异,遗憾的是似乎不能在所有情况下都使用Any
编辑:以下是生成的SQL。如您所见,它们很美丽;) ANY
... COUNT
...
似乎使用纯Where和Exists效果要比计算Count然后进行Where并将其设置为0要差得多。
请告诉我您是否在我的研究中发现了错误。无论是Any还是Count的讨论,可以从中得出的结论是,任何更复杂的LINQ在重写为存储过程时都会更好。 ;)

2
很想看看每种情况下由每个 Linq 查询生成的 Sql 查询计划。 - Pure.Krome
54
基于SQL,我只能说:这两个查询都很糟糕。我知道我通常自己编写TSQL的原因了... - Marc Gravell
任何人都必须像Count一样查看所有行。你的例子给出了如此可怕的结果有点奇怪,在最坏的情况下,!Any只应该比Count慢一点。在你的情况下,我会寻找简化选择的方法,也许将其分成几个阶段或重新排序条件(如果可能的话)。但是你的观点,即!Any比Count更好的规则不适用于!Any比Count更好,是非常好的一个观点。 - Bent

66
在.NET Framework和.NET Core中,具体细节有所不同,但这也取决于你正在做什么:如果你使用ICollectionICollection<T>类型(例如与List<T>一起使用),则有一个廉价访问的.Count属性,而其他类型可能需要枚举。 如果属性存在,请使用.Count > 0,否则使用.Any() 使用.Count() > 0从来不是最好的选择,在某些情况下,它可能会明显变慢。
这适用于.NET Framework和.NET Core。

现在我们可以深入了解细节了...

列表和集合

让我们从一个非常常见的情况开始:使用 List<T>(也是 ICollection<T>)。

.Count 属性的实现方式如下:

    private int _size;

    public int Count {
        get {
            Contract.Ensures(Contract.Result<int>() >= 0);
            return _size; 
        }
    }

这段话的意思是,_sizeAdd()Remove()等方法维护,由于它只是访问一个字段,因此这是一种极其廉价的操作——我们不需要迭代值。 ICollectionICollection<T>都有.Count属性,大多数实现它们的类型可能会以类似的方式实现。
其他IEnumerable类型(不是ICollection)需要开始枚举以确定它们是否为空。影响性能的关键因素是我们最终枚举单个项(理想情况)还是整个集合(相对昂贵)。
如果集合实际上导致I/O,例如从数据库或磁盘读取,这可能会对性能产生很大影响。

.NET Framework .Any()

在.NET Framework(4.8)中,Any()的实现如下:

public static bool Any<TSource>(this IEnumerable<TSource> source) {
    if (source == null) throw Error.ArgumentNull("source");
    using (IEnumerator<TSource> e = source.GetEnumerator()) {
        if (e.MoveNext()) return true;
    }
    return false;
}

这意味着无论如何,它都会获得一个新的枚举器对象并尝试迭代一次。这比调用List<T>.Count属性更昂贵,但至少不会遍历整个列表。
.NET Framework(4.8)中,Count()实现基本上是:
public static int Count<TSource>(this IEnumerable<TSource> source)
{
    ICollection<TSource> collection = source as ICollection<TSource>;
    if (collection != null)
    { 
        return collection.Count;
    }
    int num = 0;
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            num = checked(num + 1);
        }
        return num;
    }
}

如果可用,将使用{{ICollection.Count}},否则将枚举集合。

.NET Core .Any()

.NET Core中的LINQ Any()实现更加智能。你可以在这里看到完整的源代码,但与此讨论相关的部分如下:

    public static bool Any<TSource>(this IEnumerable<TSource> source)
    {
        //..snip..
        
        if (source is ICollection<TSource> collectionoft)
        {
            return collectionoft.Count != 0;
        }
        
        //..snip..

        using (IEnumerator<TSource> e = source.GetEnumerator())
        {
            return e.MoveNext();
        }
    }

因为List<T>ICollection<T>,所以这将调用Count属性(虽然它调用了另一个方法,但没有额外的分配)。
.NET Core的.Count()实现基本与.NET Framework相同(参见上文),因此如果可用,它将使用ICollection.Count,否则枚举集合。source

摘要

.NET Framework

  • 使用ICollection:

    • .Count > 0是最好的选择
    • .Count() > 0也可以,但最终只是调用了ICollection.Count
    • .Any()较慢,因为它会枚举单个项目
  • 对于非ICollection(没有.Count属性)

    • .Any()最好,因为它只枚举单个项目
    • .Count() > 0不好,因为它会完全枚举

.NET Core

  • .Count > 0是最好的选择,如果可用(ICollection
  • .Any()也可以,它将执行ICollection.Count > 0或枚举单个项目
  • .Count() > 0不好,因为它会完全枚举

2
谢谢您添加了.NET Framework和.NET Core之间的区别。请问您能否扩展一下,是否在.NET 5和6中发生了变化? - Rafael
2
+1 我只是在确认有人指出.Count > 0.Count() > 0不同的 - JohnB
2
您可以在此处查看基准测试结果(https://www.tabsoverspaces.com/233649-comparing-speed-of-count-0-and-any)。 - Flimtix

36

由于这是一个相当流行的话题,答案各不相同,我不得不重新审视这个问题。

测试环境:EF 6.1.3,SQL Server,300k 条记录

表模型:

class TestTable
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public string Surname { get; set; }
}

测试代码:

class Program
{
    static void Main()
    {
        using (var context = new TestContext())
        {
            context.Database.Log = Console.WriteLine;

            context.TestTables.Where(x => x.Surname.Contains("Surname")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname")).Count(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Count(x => x.Id > 1000);

            Console.ReadLine();
        }
    }
}

结果:

Any() ~ 3ms

Count() ~ 第一次查询约230ms,第二次查询约400ms

备注:

对于我的情况,EF没有生成像@Ben在他的帖子中提到的那样的SQL。


5
为了进行正确的比较,你应该执行Count() > 0。 :D - Andrew
2
安德鲁,Count() > 0 在这个特定的测试中不会与 Count() 运行不同。 - CodeMonkeyForHire

13

编辑:它已在EF版本6.1.1中修复,此答案不再适用

对于SQL Server和EF4-6,Count()的执行速度大约比Any()快两倍。

当您运行Table.Any()时,它将生成类似于(注意:不要费力思考理解它

SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent1]
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent2]
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

需要扫描两次符合条件的行。

我不喜欢写 Count() > 0 因为它隐藏了我的意图。我更喜欢使用自定义谓词来实现:

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}

我也注意到了这一点。任意(Any())SQL语句根本没有任何意义。我不确定为什么他们不这样做:CASE WHEN(EXISTS(sql)) THEN 1 ELSE 0 END。我想不出为什么他们需要执行NOT EXISTS来返回0的原因。 - scott.korin
这是错误的。你只是碰巧找到了一个糟糕的查询计划。这种情况时有发生。使用“任何”几乎总是更快的。 - usr
我检查了6.1.3生成的SQL,然后修复了它:SELECT CASE WHEN ( EXISTS (SELECT 1 AS [C1] FROM [dbo].[TestTables] AS [Extent1] WHERE [Extent1].[Id] > 1000 )) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C1] FROM ( SELECT 1 AS X ) AS [SingleRowTable1] - Ben

7

这要看数据集的大小和您的性能要求。

如果数据集不是很大,建议使用最易读的形式,对我来说任何形式都可以,因为它更短更易读,而不是一个公式。


4
如果你正在使用Entity Framework并且有一个包含大量记录的巨大表格,Any()会更快。我记得有一次我想要检查一个表格是否为空,并且它有数百万行。使用Count() > 0 花了20-30秒才完成,而使用Any()是瞬间完成的。 Any()可能会提高性能,因为它不需要迭代集合以获取数量,只需命中其中一个。或者对于像LINQ-to-Entities这样的情况,生成的SQL将是IF EXISTS(...)而不是SELECT COUNT ... 甚至 SELECT * ....

3

使用Count()来测试集合是否为空是可行的,但使用Any()可以使意图更清晰,代码更易读。然而,有些情况需要特别注意:

如果集合是EntityFramework或其他ORM查询,则调用Count()将导致执行潜在的大规模SQL查询,并可能对应用程序数据库造成巨大的开销。调用Any()也会连接到数据库,但会生成更高效的SQL。

如果集合是LINQ查询的一部分,其中包含创建对象的Select()语句,则可能会不必要地分配大量内存。调用Any()将更加高效,因为它会执行较少的枚举迭代。

使用Any()的示例:

private static bool IsEmpty(IEnumerable<string> strings)
{
  return !strings.Any();
}

3
你可以进行一个简单的测试来解决这个问题:
var query = //make any query here
var timeCount = new Stopwatch();
timeCount.Start();
if (query.Count > 0)
{
}
timeCount.Stop();
var testCount = timeCount.Elapsed;

var timeAny = new Stopwatch();
timeAny.Start();
if (query.Any())
{
}
timeAny.Stop();
var testAny = timeAny.Elapsed;

检查testCount和testAny的值。


1
这里是使用Count属性和Any()方法进行测试的代码 Count属性胜出,速度比Any()方法快2倍 - Stanislav Prusac
1
为了获得更好的结果,您可以进行1000次(或更多)这些比较。这有助于平均结果并避免任何随机波动。 - Roman
当你像上述方法一样进行测试时,你需要考虑更多的因素,比如数据库/网络的负载,数据库端的计划缓存等。因此,为了进行准确的测试,你应该设计一个隔离和准确的环境。 - Vahid Farahmandian
为更好地进行比较,应将“Count”替换为方法Count()与.Any()不是属性。您需要迭代的时间。 - daremachine

2
关于Count()方法,如果IEnumerable是一个ICollection,那么我们无法遍历所有项,因为我们可以检索ICollectionCount字段。如果IEnumerable不是一个ICollection,我们必须使用whileMoveNext来遍历所有项,请看.NET Framework代码:
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;
}

参考文献:参考源可枚举


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