哪种方法执行得更好:. any()vs. Count()>0?

System.Linq命名空间中,我们现在可以将iemumable的扩展为任何()计数()扩展方法

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

其次,一些集合有财产(不是扩展方法),即CountLength。使用这些而不是.Any().Count()会更好吗?

是/不是?

212445 次浏览

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

对于IEnumerable<T>,那么Any()一般更快,因为它只需要查看一次迭代。然而,请注意,Count()的LINQ-to-Object实现确实检查ICollection<T>(使用.Count作为优化)-所以如果你的底层数据源是直接一个列表/集合,那么不会有太大的差异。不要问我为什么它不使用非泛型的ICollection

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

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

备注:我在Entity Framework 4发布时写了这个答案。这个答案的重点不是进入琐碎的.Any() vs.Count()性能测试。重点是表明EF远非完美。较新版本更好……但是如果你有一部分代码很慢并且它使用EF,请使用直接TSQL测试并比较性能,而不是依赖于假设(.Any()总是比.Count() > 0快)。


虽然我同意大多数投票的答案和评论-特别是在Any信号开发者意图Count() > 0更好的问题上-我遇到过在SQL服务器(EntityFramework 4)上计数更快的情况。

这是使用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();

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的纯where比计算Count然后使用Count==0执行where要糟糕得多。

如果你们在我的发现中看到一些错误,请告诉我。无论任何与计数的讨论如何,都可以从这一切中删除的是,任何更复杂的LINQ在重写为存储过程时都要好得多;)。

编辑:它在EF版本6.1.1中得到了修复。

对于SQLServer和EF4-6,Count()的执行速度大约是任何()的两倍。

当你运行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;
}
}

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

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

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

测试环境: EF 6.1.3,SQL服务器,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

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

备注:

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

你可以做一个简单的测试来解决这个问题:

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

关于计数()方法,如果IEnumarableICollection,那么我们不能遍历所有项,因为我们可以检索ICollection计数字段,如果可数不是ICollection,我们必须使用移动下一步遍历所有项,看看. 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;
}

参考:引用源可枚举

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

任何()可以是性能增强,因为它可能不必迭代集合来获取事物的数量。它只需要命中其中一个。或者,对于LINQ到实体,生成的SQL将是IF EXISTS(…)而不是SELECT COUNT…甚至SELECT*…。

. NET Framework与. NET Core的确切细节略有不同,但它也在一定程度上取决于您正在做的事情:如果您使用ICollectionICollection<T>类型(例如List<T>),则有一个.Count属性访问起来很便宜,而其他类型可能需要枚举。

太长别读:

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

这意味着_sizeAdd()Remove()等维护,由于它只是访问一个字段,这是一个非常便宜的操作——我们不需要迭代值。

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

其他数字

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

如果收集实际上导致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中的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();
}
}

因为List<T>ICollection<T>,这将调用Count属性(尽管它调用了另一个方法,但没有额外的分配)。

. NET Core.Count()

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


总结

. NET Framework

  • 使用ICollection

    • .Count > 0是最好的
    • .Count() > 0很好,但最终只是调用ICollection.Count
    • .Any()会更慢,因为它枚举了一个项目
  • 使用非ICollection(no.Count属性)

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

. NET Core

  • .Count > 0是最好的,如果可用(ICollection
  • .Any()很好,可以执行ICollection.Count > 0或枚举单个项目
  • .Count() > 0不好,因为它导致完全枚举

我使用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; }
}

结果: 输入图片描述

任何东西都比计数好。

使用Count()测试空性可以工作,但使用Any()可以使意图更清晰,代码更具可读性。但是,在某些情况下应特别注意:

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

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

使用Any()的示例:

private static bool IsEmpty(IEnumerable<string> strings)
{
return !strings.Any();
}