哪种方法效果更好:.Any()与.Count()> 0?


576

System.Linq名称空间中,我们现在可以将IEnumerable扩展为具有Any()Count() 扩展方法

最近有人告诉我,如果我要检查集合中是否包含1个或多个项目,则应该使用.Any()扩展方法而不是.Count() > 0扩展方法,因为.Count()扩展方法必须遍历所有项目。

其次,一些集合具有属性(未扩展方法),其是CountLength。使用这些代替而不是.Any()或会更好.Count()

是/否?


最好在Enumerables上使用Any()并在Collection上使用Count。如果有人觉得写'(somecollection.Count> 0)'会引起混乱或引起可读性问题,最好将其写为扩展方法Any()。然后每个人都满意。性能方面以及可读性方面。这样您的所有代码都将具有一致性,并且项目中的单个开发人员无需担心选择Count vs Any。
Mahesh Bongani

Answers:


708

如果你开始的东西,有一个.Length.Count(如ICollection<T>IList<T>List<T>,等) -那么这将是最快的选择,因为它不需要去通过GetEnumerator()/ MoveNext()/ Dispose()所要求的顺序Any(),检查是否有非空IEnumerable<T>序列。

对于刚刚IEnumerable<T>,然后Any()通常更快,因为它只有看一次迭代。但是,请注意,的LINQ-to-Objects实现Count()确实进行了检查ICollection<T>.Count用作优化)-因此,如果您的基础数据源直接是列表/集合,则不会有很大的不同。不要问我为什么不使用非通用ICollection...

当然,如果您使用LINQ对其进行过滤(Where等等),则将具有基于迭代器块的序列,因此此ICollection<T>优化是无用的。

通常与IEnumerable<T>:坚持使用; Any()-p


9
马克:ICollection <T>实际上不是从ICollection派生的。我也很惊讶,但是Reflector并没有说谎。
布赖恩·瓦茨

7
Any()实现不检查ICollection接口并在之后检查Count属性吗?
derigel

313
我认为大多数情况下还有使用Any()的另一个原因。它表示开发人员的确切意图。如果您不希望知道项目的数量,而只是想知道项目的数量,那么somecollection.Any()比somecollection.Count更简单明了。计数> 0
TJKjaer 2010年

13
@huttelihut-您知道多少开发人员真正被该声明弄糊涂了(somecollection.Count > 0)?在引入LINQ的.Any()方法之前,我们所有的代码是否难以理解?
CraigTP

25
@JLRishe-我仍然感觉someCollection.Count > 0很清晰,someCollection.Any()并且具有更高的性能以及不需要LINQ的额外好处。当然,这是一个非常简单的情况,使用LINQ运算符的其他构造将使开发人员的意图比等效的非LINQ选项更加清晰。
CraigTP 2014年

65

注意:当实体框架4实际存在时,我写了这个答案。这个答案的要点是不要进入琐碎.Any()VS .Count()性能测试。关键是要表明EF远非完美。较新的版本会更好...但是,如果您的部分代码很慢并且使用EF,请使用直接TSQL进行测试并比较性能,而不要依赖假设(.Any()总比更快.Count() > 0)。


尽管我同意大多数投票赞成的答案和评论-特别是在点上Any表明开发人员的意图要好于Count() > 0-我遇到了这样的情况:在SQL Server上,Count的数量级更快(EntityFramework 4)。

这是带有Anythew超时异常的查询(约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

exec sp_executesql N'选择顶部(1) 
[Project2]。[ContactId] AS [ContactId], 
[Project2]。[CompanyId] AS [CompanyId], 
[Project2]。[ContactName] AS [ContactName], 
[Project2]。[FullName] AS [FullName], 
[Project2]。[ContactStatusId] AS [ContactStatusId], 
[Project2]。[创建] AS [创建]
从(选择[Project2]。[ContactId] AS [ContactId],[Project2]。[CompanyId] AS [CompanyId],[Project2]。[ContactName] AS [ContactName],[Project2]。[FullName] AS [FullName] ,[Project2]。[ContactStatusId] AS [ContactStatusId],[Project2]。[Created] AS [Created],row_number()OVER(ORDER BY [Project2]。[ContactId] ASC)AS [row_number]
    从(选择 
        [Extent1]。[ContactId] AS [ContactId], 
        [Extent1]。[CompanyId] AS [CompanyId], 
        [Extent1]。[ContactName] AS [ContactName], 
        [Extent1]。[FullName] AS [FullName], 
        [Extent1]。[ContactStatusId] AS [ContactStatusId], 
        [范围1]。[创建] AS [创建]
        来自[dbo]。[联系方式]为[范围1]
        在([Extent1]。[CompanyId] = @ p__linq__0)和([[Extent1]。[ContactStatusId] <= 3)和(不存在(选择 
            1 AS [C1]
            从[dbo]。[NewsletterLog] AS [Extent2]
            在([Extent1]。[ContactId] = [Extent2]。[ContactId])和(6 = [Extent2]。[NewsletterLogTypeId])中
        ))
    )AS [Project2]
)AS [Project2]
在[Project2]中。[行号]> 99
ORDER BY [Project2]。[ContactId] ASC',N'@ p__linq__0 int',@ p__linq__0 = 4

COUNT

exec sp_executesql N'选择顶部(1) 
[Project2]。[ContactId] AS [ContactId], 
[Project2]。[CompanyId] AS [CompanyId], 
[Project2]。[ContactName] AS [ContactName], 
[Project2]。[FullName] AS [FullName], 
[Project2]。[ContactStatusId] AS [ContactStatusId], 
[Project2]。[创建] AS [创建]
从(选择[Project2]。[ContactId] AS [ContactId],[Project2]。[CompanyId] AS [CompanyId],[Project2]。[ContactName] AS [ContactName],[Project2]。[FullName] AS [FullName] ,[Project2]。[ContactStatusId] AS [ContactStatusId],[Project2]。[Created] AS [Created],row_number()OVER(ORDER BY [Project2]。[ContactId] ASC)AS [row_number]
    从(选择 
        [Project1]。[ContactId] AS [ContactId], 
        [Project1]。[CompanyId] AS [CompanyId], 
        [Project1]。[ContactName] AS [ContactName], 
        [Project1]。[FullName] AS [FullName], 
        [Project1]。[ContactStatusId] AS [ContactStatusId], 
        [Project1]。[创建] AS [创建]
        从(选择 
            [Extent1]。[ContactId] AS [ContactId], 
            [Extent1]。[CompanyId] AS [CompanyId], 
            [Extent1]。[ContactName] AS [ContactName], 
            [Extent1]。[FullName] AS [FullName], 
            [Extent1]。[ContactStatusId] AS [ContactStatusId], 
            [Extent1]。[Created] AS [Created], 
            (选择 
                COUNT(1)AS [A1]
                从[dbo]。[NewsletterLog] AS [Extent2]
                其中([Extent1]。[ContactId] = [Extent2]。[ContactId])AND(6 = [Extent2]。[NewsletterLogTypeId]))AS [C1]
            来自[dbo]。[联系方式]为[范围1]
        )AS [Project1]
        在([Project1]。[CompanyId] = @ p__linq__0)和([Project1]。[ContactStatusId] <= 3)和(0 = [Project1]。[C1])
    )AS [Project2]
)AS [Project2]
在[Project2]中。[行号]> 99
ORDER BY [Project2]。[ContactId] ASC',N'@ p__linq__0 int',@ p__linq__0 = 4

似乎使用EXISTS进行纯Where运算要比计算Count然后使用Count == 0进行Where运算要差得多。

让我知道你们是否发现我的发现有误。不管“任何与计数”的讨论如何,所有这些都可以得出的结论是,将更复杂的LINQ重写为存储过程时会更好;)。


2
很想看看每个方案针对每个linq查询生成的一些SQL查询计划。
Pure.Krome 2012年

43
基于SQL,我只能说:这两个查询看起来都很糟糕。我知道有一个原因我通常会编写自己的TSQL ...
Marc Gravell

任何人都必须像Count一样浏览所有行。您的示例给出如此可怕的结果有点奇怪,在最坏的情况下!任何东西都只比Count慢一点。在您的情况下,我将寻找简化选择的方法,如果可能的话,也许分阶段将其拆分或重新排列条件。但是,您认为“任何一个都比Count更好”规则并不成立!任何一个都比Count更好是一个很好的规则。
弯曲

25

由于这是一个非常受欢迎的话题,并且答案各不相同,因此我不得不重新审视这个问题。

测试环境: EF 6.1.3,SQL Server,30万条记录

桌子型号

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()〜3毫秒

Count()〜230ms用于第一次查询,〜400ms用于第二次查询

备注:

就我而言,EF没有像他的帖子中提到的@Ben那样生成SQL。


4
为了进行适当的比较,您应该这样做Count() > 0。:D
安德鲁

1
安德鲁,在此特定测试中,Count()> 0与Count()的运行不会有所不同。
CodeMonkeyForHire

11

编辑:在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]

需要根据情况扫描2行。

我不喜欢写,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(存在(从[dbo]中选择1 AS [C1]。[TestTables] AS [Extent1] WHERE [Extent1]。[Id]> 1000))然后浇铸(1作为位)ELSE铸造(0作为位)END AS [C1] FROM(SELECT 1 AS X)AS [SingleRowTable1]

6

这取决于数据集的大小以及您对性能的要求是什么?

如果没什么大不了的,请使用最易读的形式,对我自己来说是任何形式,因为它更短,更易读,而不是方程式。


2

关于计数()方法,如果IEnumarableICollection的,那么我们不能迭代的所有项目,因为我们可以检索计数的领域ICollection的,如果IEnumerable的是不是一个ICollection的,我们必须重复的所有项目使用,而用一个MoveNext,看看.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;
}

参考:参考源可枚举


2

您可以进行简单的测试来弄清楚这一点:

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属性VS任何代码测试()Count属性胜VS任何()与2X + - 链接
斯坦尼斯Prusac

1
为了获得更好的结果,您可以将这些比较进行1000次(或更多)。它有助于平均结果并避免任何随机峰值。
罗马,

当像上述方法一样进行测试时,您需要考虑更多因素,例如数据库/网络上的负载,计划数据库侧的缓存等。因此,要进行准确的测试,您还应该设计一个隔离且准确的环境
Vahid Farahmandian

为了更好地进行比较,应使用CountCount()与.Any()方法代替,而不是属性。您需要迭代时间。
daremachine

0

如果您使用的是Entity Framework,并且有一个包含许多记录的巨大表,那么Any()将会更快。我记得有一次我想检查一个表是否为空并且有数百万行。完成Count()> 0花费了20-30秒。Any()是瞬间的。

Any()可以提高性能,因为它可能不必迭代集合来获取事物的数量。它只需要打其中之一。或者,例如对于LINQ-to-Entities,生成的SQL将是IF EXISTS(...),而不是SELECT COUNT ...甚至SELECT *...。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.