在System.Linq
命名空间中,我们现在可以扩展我们的IEnumerable
以使用Any()
和Count()
扩展方法。
我最近被告知如果我想检查一个集合中是否包含一个或多个项目,我应该使用.Any()
扩展方法而不是.Count() > 0
扩展方法,因为{{1扩展方法必须遍历所有项目。
其次,某些集合具有.Count()
或Count
的属性(不是扩展方法)。使用它们会更好,而不是Length
或.Any()
吗?
答案 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 - 但显然在某些情况下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
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的确切详细信息有所不同,但是它在某种程度上还取决于您在做什么:如果您使用的是ICollection
或ICollection<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;
}
}
_size
是由Add()
,Remove()
等维护的,由于它只是访问一个字段,因此这是非常便宜的操作-我们不需要遍历值。
ICollection
和ICollection<T>
都具有实现它们的.Count
和 most 类型,它们可能以类似的方式实现。
不是IEnumerable
的所有其他ICollection
类型也需要启动枚举以确定它们是否为空。影响性能的关键因素是我们最终要枚举单个项目(理想)还是整个集合(相对昂贵)。
如果该集合实际上是在引起I / O(例如,通过从数据库或磁盘读取),则可能会对性能造成重大影响。
.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
属性要昂贵,但至少不会迭代整个列表。
.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
,否则将枚举集合。
.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
属性(尽管它调用了另一个方法,但没有额外的分配)。
.Count()
.NET Core实现(source)与.NET Framework(请参见上文)基本相同,因此将在可用的情况下使用ICollection.Count
,否则将枚举集合。
使用ICollection
:
.Count > 0
最好.Count() > 0
很好,但最终只调用ICollection.Count
.Any()
将会变慢,因为它会枚举单个项目具有非ICollection
(无.Count
属性)
.Any()
最好,因为它只枚举单个项目.Count() > 0
不好,因为它会导致完整的枚举.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;
}
答案 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; }
}
任何都比计数好。