首页 文章

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

提问于
浏览
513

System.Linq 命名空间中,我们现在可以扩展 IEnumerable 以使用 Any()Count() 扩展方法 .

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

其次,一些集合具有 CountLength 的属性(不是扩展方法) . 使用它们会更好吗,而不是 .Any().Count()

是啊/是?

8 回答

  • 2

    好吧, .Count() 扩展方法不会使用 .Count 属性,但我会假设您不会将 .Count() 方法用于简单集合,而是使用带有过滤条件的LINQ语句的末尾等 .

    在这种情况下, .Any() 将比 .Count() > 0 更快 .

  • 16

    您可以进行简单的测试来解决这个问题:

    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的值 .

  • 623

    关于 Count() 方法,如果 IEnumarableICollection ,那么我们不能遍历所有项目,因为我们可以检索 ICollectionCount 字段,如果 IEnumerable 不是 ICollection ,我们必须使用带有 MoveNextwhile 迭代所有项目,看看.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;
    }
    

    参考:Reference Source Enumerable

  • 56

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

    如果它没有什么巨大的使用最可读的形式,这对我来说是任何形式,因为它更短,更易读,而不是方程式 .

  • 5

    EDIT: 它已在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;
        }
    }
    
  • 10

    如果您从具有 .Length.Count (例如 ICollection<T>IList<T>List<T> 等)的东西开始 - 那么这将是最快的选项,因为它不需要通过所需的 GetEnumerator() / MoveNext() / Dispose() 序列 . Any() 检查非空 IEnumerable<T> 序列 .

    对于 IEnumerable<T> ,那么 Any() 通常会更快,因为它只需要查看一次迭代 . 但是,请注意 Count() 的LINQ-to-Objects实现检查 ICollection<T> (使用 .Count 作为优化) - 所以如果您的基础数据源是 directly 列表/集合,那么就赢了't be a huge difference. Don' t问我为什么不这样做使用非泛型 ICollection ...

    当然,如果你已经使用LINQ过滤它等( Where 等),你将有一个基于迭代器块的序列,所以这个 ICollection<T> 优化是没用的 .

    一般用 IEnumerable<T> :坚持 Any() ;-p

  • 7

    由于这是相当受欢迎的主题和答案不同,我不得不重新审视问题 .

    Testing env: EF 6.1.3,SQL Server,300k记录

    Table model

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

    Test code:

    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();
            }
        }
    }
    

    Results:

    任何()~3ms

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

    Remarks:

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

  • 1

    Note: 我在实体框架4实际时写了这个答案 . 这个答案的要点不是要进行微不足道的 .Any() vs .Count() 性能测试 . 关键是要表明EF远非完美 . 较新的版本更好......但是如果你的代码部分速度很慢且使用EF,则使用直接TSQL进行测试并比较性能而不是依赖于假设( .Any() 总是比 .Count() > 0 快) .


    虽然我同意大多数最高投票的答案和评论 - 特别是关于 Any 表明开发人员的意图比_233144更好 - 我的情况是,SQL在SQL Server(EntityFramework 4)上的数量级更快 .

    这是查询 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

    exec sp_executesql N'SELECT TOP (1) 
    [Project2].[ContactId] AS [ContactId], 
    [Project2].[CompanyId] AS [CompanyId], 
    [Project2].[ContactName] AS [ContactName], 
    [Project2].[FullName] AS [FullName], 
    [Project2].[ContactStatusId] AS [ContactStatusId], 
    [Project2].[Created] AS [Created]
    FROM ( SELECT [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]
        FROM ( SELECT 
            [Extent1].[ContactId] AS [ContactId], 
            [Extent1].[CompanyId] AS [CompanyId], 
            [Extent1].[ContactName] AS [ContactName], 
            [Extent1].[FullName] AS [FullName], 
            [Extent1].[ContactStatusId] AS [ContactStatusId], 
            [Extent1].[Created] AS [Created]
            FROM [dbo].[Contact] AS [Extent1]
            WHERE ([Extent1].[CompanyId] = @p__linq__0) AND ([Extent1].[ContactStatusId] <= 3) AND ( NOT EXISTS (SELECT 
                1 AS [C1]
                FROM [dbo].[NewsletterLog] AS [Extent2]
                WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId])
            ))
        )  AS [Project2]
    )  AS [Project2]
    WHERE [Project2].[row_number] > 99
    ORDER BY [Project2].[ContactId] ASC',N'@p__linq__0 int',@p__linq__0=4
    

    COUNT

    exec sp_executesql N'SELECT TOP (1) 
    [Project2].[ContactId] AS [ContactId], 
    [Project2].[CompanyId] AS [CompanyId], 
    [Project2].[ContactName] AS [ContactName], 
    [Project2].[FullName] AS [FullName], 
    [Project2].[ContactStatusId] AS [ContactStatusId], 
    [Project2].[Created] AS [Created]
    FROM ( SELECT [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]
        FROM ( SELECT 
            [Project1].[ContactId] AS [ContactId], 
            [Project1].[CompanyId] AS [CompanyId], 
            [Project1].[ContactName] AS [ContactName], 
            [Project1].[FullName] AS [FullName], 
            [Project1].[ContactStatusId] AS [ContactStatusId], 
            [Project1].[Created] AS [Created]
            FROM ( SELECT 
                [Extent1].[ContactId] AS [ContactId], 
                [Extent1].[CompanyId] AS [CompanyId], 
                [Extent1].[ContactName] AS [ContactName], 
                [Extent1].[FullName] AS [FullName], 
                [Extent1].[ContactStatusId] AS [ContactStatusId], 
                [Extent1].[Created] AS [Created], 
                (SELECT 
                    COUNT(1) AS [A1]
                    FROM [dbo].[NewsletterLog] AS [Extent2]
                    WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId])) AS [C1]
                FROM [dbo].[Contact] AS [Extent1]
            )  AS [Project1]
            WHERE ([Project1].[CompanyId] = @p__linq__0) AND ([Project1].[ContactStatusId] <= 3) AND (0 = [Project1].[C1])
        )  AS [Project2]
    )  AS [Project2]
    WHERE [Project2].[row_number] > 99
    ORDER BY [Project2].[ContactId] ASC',N'@p__linq__0 int',@p__linq__0=4
    

    似乎纯粹的EXISTS在哪里工作比计算Count更糟糕,然后用Count == 0做Where .

    如果你们发现我的调查结果有误,请告诉我 . 无论Any vs Count讨论如何,可以从所有这些中解脱出来的是,当重写为存储过程时,任何更复杂的LINQ都会更好;) .

相关问题