662

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

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

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

是的/不?

4

11 回答 11

804

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

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

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

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

于 2008-11-20T12:37:04.550 回答
68

注意:我在 Entity Framework 4 实际出现时写了这个答案。这个答案的重点不是要进行微不足道的.Any().Count()性能测试。关键是要表明 EF 远非完美。较新的版本更好......但是如果您有部分代码很慢并且它使用 EF,请使用直接 TSQL 进行测试并比较性能,而不是依赖假设(.Any()总是比 更快.Count() > 0)。


虽然我同意大多数赞成的答案和评论 - 特别是在这一点上Any表明开发人员的意图Count() > 0- 我遇到过 Count 在 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],
[项目 2].[全名] 作为 [全名],
[Project2].[ContactStatusId] AS [ContactStatusId],
[项目2].[已创建] AS [已创建]
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]
    从(选择
        [Extent1].[ContactId] AS [ContactId],
        [Extent1].[CompanyId] AS [CompanyId],
        [Extent1].[ContactName] AS [ContactName],
        [Extent1].[FullName] AS [FullName],
        [Extent1].[ContactStatusId] AS [ContactStatusId],
        [Extent1].[已创建] AS [已创建]
        FROM [dbo].[Contact] AS [Extent1]
        WHERE ([Extent1].[CompanyId] = @p__linq__0) AND ([Extent1].[ContactStatusId] <= 3) AND ( 不存在 (SELECT
            1 作为 [C1]
            FROM [dbo].[NewsletterLog] AS [Extent2]
            WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId])
        ))
    ) 作为 [项目2]
) 作为 [项目2]
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],
[项目 2].[全名] 作为 [全名],
[Project2].[ContactStatusId] AS [ContactStatusId],
[项目2].[已创建] AS [已创建]
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]
    从(选择
        [Project1].[ContactId] AS [ContactId],
        [Project1].[CompanyId] AS [CompanyId],
        [Project1].[ContactName] AS [ContactName],
        [项目 1].[全名] 作为 [全名],
        [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],
            (选择
                计数(1) 作为 [A1]
                FROM [dbo].[NewsletterLog] AS [Extent2]
                WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId])) 作为 [C1]
            FROM [dbo].[Contact] AS [Extent1]
        ) 作为 [项目1]
        WHERE ([Project1].[CompanyId] = @p__linq__0) AND ([Project1].[ContactStatusId] <= 3) AND (0 = [Project1].[C1])
    ) 作为 [项目2]
) 作为 [项目2]
WHERE [Project2].[row_number] > 99
ORDER BY [Project2].[ContactId] ASC',N'@p__linq__0 int',@p__linq__0=4

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

如果你们在我的发现中发现一些错误,请告诉我。不管任何与计数的讨论如何,从这一切中可以得出的结论是,任何更复杂的 LINQ 在重写为存储过程时会更好;)。

于 2012-06-14T23:22:06.993 回答
34

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

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

结果:

任何()〜3毫秒

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

评论:

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

于 2015-05-28T08:14:57.683 回答
33

.NET Framework 与 .NET Core 的具体细节略有不同,但也取决于您在做什么:如果您使用的是ICollectionorICollection<T>类型(例如 with List<T>),则有一个.Count访问成本较低的属性,而其他类型可能需要枚举。

TL;博士:

如果属性存在则使用.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()并且由于它只是访问一个字段,这是一个非常便宜的操作——我们不需要迭代值。

ICollection并且ICollection<T>两者都有.Count并且大多数实现它们的类型都可能以类似的方式这样做。

其他 IEnumerables

任何其他不需要IEnumerable的类型也ICollection需要开始枚举以确定它们是否为空。影响性能的关键因素是我们最终是枚举单个项目(理想)还是整个集合(相对昂贵)。

如果集合实际上导致了 I/O,例如通过从数据库或磁盘读取,这可能会对性能造成很大影响。


.NET 框架.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 框架.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 核心.Any()

.NET Core 中的 LINQAny()实现要智能得多。您可以在此处查看完整的源代码,但可以查看与此讨论相关的部分:

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

因为 aList<T>是 an ICollection<T>,这将调用Count属性(虽然它调用另一个方法,但没有额外的分配)。

.NET 核心.Count()

.NET Core 实现(source)与 .NET Framework(见上文)基本相同,因此ICollection.Count如果可用,它将使用,否则枚举集合。


概括

.NET 框架

  • ICollection

    • .Count > 0是最好的
    • .Count() > 0很好,但最终只是打电话ICollection.Count
    • .Any()会变慢,因为它枚举单个项目
  • 与非ICollection(无.Count财产)

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

.NET 核心

  • .Count > 0最好,如果有的话 ( ICollection)
  • .Any()很好,并且会做ICollection.Count > 0或枚举单个项目
  • .Count() > 0不好,因为它会导致完整的枚举
于 2020-08-24T22:41:39.007 回答
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]

这需要根据您的情况对行进行 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;
    }
}
于 2014-01-03T16:32:26.523 回答
7

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

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

于 2014-12-22T23:43:13.713 回答
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 的值。

于 2017-01-20T17:15:51.127 回答
3

如果您使用的是实体框架并且有一个包含许多记录的巨大表,那么Any()会快得多。我记得有一次我想检查一张表是否为空,是否有数百万行。Count() > 0 需要 20-30 秒才能完成。Any()是即时的。

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

于 2019-03-11T05:05:40.123 回答
2

关于Count()方法,如果IEnumarableICollection,那么我们不能遍历所有项目,因为我们可以检索 ICollection 的Count字段,如果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;
}

参考:参考源可枚举

于 2018-03-28T17:35:36.180 回答
-1

使用Count()测试空性有效,但使用Any()使意图更清晰,代码更具可读性。但是,有些情况需要特别注意:

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

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

使用示例Any()

private static bool IsEmpty(IEnumerable<string> strings)
{
  return !strings.Any();
}
于 2021-11-18T13:31:54.393 回答
-2

我使用 IList 创建了一个示例应用程序,其中包含 100 个元素到 100 万个项目,以查看最好的 Count vs 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; }
}

结果 : 在此处输入图像描述

任何都比计数好。

于 2021-06-15T05:02:19.770 回答