在系统中。Linq命名空间,我们现在可以扩展我们的IEnumerable来拥有Any()和Count()扩展方法。

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

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

-是的-不?


当前回答

具体细节在。net Framework和。net Core中略有不同,但这也在一定程度上取决于你在做什么:如果你正在使用ICollection或ICollection<T>类型(例如List<T>),则有一个. count属性易于访问,而其他类型可能需要枚举。

TL; diana:

如果属性存在,则使用. 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,大多数实现它们的类型都可能以类似的方式这样做。

其他ienumerable

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

如果收集实际上导致了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>代价更大。属性,但至少它不是迭代整个列表。

.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()实现要聪明得多。你可以在这里看到完整的源代码,但与此讨论相关的部分:

    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实现(源代码)基本上与. net Framework(见上文)相同,因此它将使用ICollection。如果可用则计数,否则枚举集合。


总结

net框架

ICollection: 计数> 0是最好的 . count() > 0很好,但最终只调用ICollection。数 .Any()会比较慢,因为它枚举单个项 使用非icollection (no .Count属性) .Any()是最好的,因为它只枚举单个项 .Count() > 0是坏的,因为它导致完全枚举

net核心

.Count > 0是最好的,如果可用(ICollection) . any()是很好的,将会做ICollection。计数> 0或枚举单个项目 .Count() > 0是坏的,因为它导致完全枚举

其他回答

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

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


虽然我同意大多数赞成的答案和评论-特别是在这一点上,任何信号开发人员的意图都比Count() > 0 -我有过这样的情况,在SQL Server (EntityFramework 4)上,Count的速度要快一个数量级。

下面是使用Any查询的w超时异常(在~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();

以毫秒为单位执行的计数版本:

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 -但很明显,在某些情况下,Count和Any之间存在巨大的性能差异,不幸的是,似乎您不能在所有情况下都坚持使用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

数:

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

似乎纯粹的Where with EXISTS比计算Count然后用Count == 0执行Where要差得多。

如果你们发现我的发现有错误,请告诉我。不管Any vs Count的讨论如何,我们可以从所有这些中得出的结论是,任何更复杂的LINQ在重写为存储过程时都要好得多。

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

测试env: 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。

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

如果不是很大,就用最易读的形式, 这对我来说是任何,因为它比方程更短更易读。

如果您从具有. length或. count的东西开始(例如ICollection<T>, IList<T>, List<T>,等等),那么这将是最快的选项,因为它不需要通过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