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

时间:2008-11-20 12:11:19

标签: .net linq performance .net-3.5 extension-methods

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

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

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

是的/是吗?

10 个答案:

答案 0 :(得分:658)

如果您开始使用.Length.Count(例如ICollection<T>IList<T>List<T>等),那么这将是是最快的选择,因为它不需要通过GetEnumerator()所需的MoveNext() / Dispose() / Any()序列来检查非空{{1}序列。

仅对于IEnumerable<T>IEnumerable<T> 通常会更快,因为它只需要查看一次迭代。但请注意,Any()的LINQ到对象实现会检查Count()(使用ICollection<T>作为优化) - 因此,如果您的基础数据源直接< / strong>列表/集合,不会有太大的差异。不要问我为什么它不使用非通用的.Count ...

当然,如果您使用LINQ过滤它(ICollection等),您将有一个基于迭代器块的序列,因此这个Where优化是无用的。

一般情况下ICollection<T>:坚持IEnumerable<T> ;-p

答案 1 :(得分:61)

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


虽然我同意大多数最高投票的答案和评论 - 特别是关于点Any信号开发者意图优于Count() > 0 - 我有过这样的情况在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的地方做。

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

答案 2 :(得分:21)

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

测试环境: 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();
        }
    }
}

<强>结果:

任何()~3ms

第一次查询时计数()~230ms,第二次查询约400毫秒

<强>说明:

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

答案 3 :(得分: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;
    }
}

答案 4 :(得分:7)

.NET Framework与.NET Core的确切详细信息有所不同,但是它在某种程度上还取决于您在做什么:如果您使用的是ICollectionICollection<T>类型(例如与List<T>一样),有一个.Count属性很便宜,而其他类型可能需要枚举。

TL; DR:

如果属性存在,则使用.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; 
        }
    }

_size是由Add()Remove()等维护的,由于它只是访问一个字段,因此这是非常便宜的操作-我们不需要遍历值。

ICollectionICollection<T>都具有实现它们的.Count most 类型,它们可能以类似的方式实现。

其他IEnumerables

不是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 .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()实现要聪明得多。您可以看到complete source here,但可以看到此讨论的相关内容:

    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 Core实现(source)与.NET Framework(请参见上文)基本相同,因此将在可用的情况下使用ICollection.Count,否则将枚举集合。


摘要

.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不好,因为它会导致完整的枚举

答案 5 :(得分:5)

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

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

答案 6 :(得分: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的值。

答案 7 :(得分:1)

关于 Count()方法,如果 IEnumarable ICollection ,那么我们就无法遍历所有项目,因为我们可以检索 ICollection 计数字段,如果 IEnumerable 不是 ICollection ,我们必须遍历所有项目使用 while 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;
}

参考:Reference Source Enumerable

答案 8 :(得分:0)

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

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

答案 9 :(得分:-1)

我使用 IList 创建了一个示例应用程序,其中包含 100 个元素到 100 万个项目,以查看 Count 与 Any 哪个最好。

代码

class Program
{
    static void Main()
    {

        //Creating List of customers
        IList<Customer> customers = new List<Customer>();
        for (int i = 0; i <= 100; i++)
        {
            Customer customer = new Customer
            {
                CustomerId = i,
                CustomerName = string.Format("Customer{0}", i)
            };
            customers.Add(customer);
        }

        //Measuring time with count
        Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();
        if (customers.Count > 0)
        {
            Console.WriteLine("Customer list is not empty with count");
        }
        stopWatch.Stop();
        Console.WriteLine("Time consumed with count: {0}", stopWatch.Elapsed);

        //Measuring time with any
        stopWatch.Restart();
        if (customers.Any())
        {
            Console.WriteLine("Customer list is not empty with any");
        }
        stopWatch.Stop();
        Console.WriteLine("Time consumed with count: {0}", stopWatch.Elapsed);
        Console.ReadLine();

    }
}

public class Customer
{
    public int CustomerId { get; set; }
    public string CustomerName { get; set; }
}

结果: enter image description here

任何都比计数好。