I often see .Count() > 0
or .Count > 0
to check if there are any elements in some collection. Linq provides an Any()
method to check if there are any elements. Any()
sure reads better but does it come with any downsides? Inspired by a twitter post on using Any()
and benchmarking it, I decided to run some of my own tests using BenchmarkDotNet.
The benchmarks that were run compare the use of these two methods, comparing enumerables and lists with two sizes, 1000 and 1000.
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.985 (20H2/October2020Update)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.203
[Host] : .NET 5.0.6 (5.0.621.22011), X64 RyuJIT
DefaultJob : .NET 5.0.6 (5.0.621.22011), X64 RyuJIT
Method | N | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|---|
Enumerable_Any | 1000 | 12.7297 ns | 0.2874 ns | 0.2400 ns | 12.7911 ns | - | - | - | - |
Enumerable_CountGreaterThanZero | 1000 | 83,476.7798 ns | 1,015.1761 ns | 949.5963 ns | 83,264.1113 ns | - | - | - | - |
List_Any | 1000 | 5.6846 ns | 0.1155 ns | 0.1080 ns | 5.6549 ns | - | - | - | - |
List_CountGreaterThanZero | 1000 | 0.0248 ns | 0.0268 ns | 0.0358 ns | 0.0000 ns | - | - | - | - |
Enumerable_Any | 10000 | 12.5423 ns | 0.1560 ns | 0.1382 ns | 12.5044 ns | - | - | - | - |
Enumerable_CountGreaterThanZero | 10000 | 835,227.9053 ns | 5,795.2965 ns | 4,524.5863 ns | 834,107.7637 ns | - | - | - | - |
List_Any | 10000 | 5.5781 ns | 0.1236 ns | 0.1423 ns | 5.5443 ns | - | - | - | - |
List_CountGreaterThanZero | 10000 | 0.0106 ns | 0.0153 ns | 0.0143 ns | 0.0000 ns | - | - | - | - |
It's pretty safe to say that Count() > 0
on an enumerable is pretty slow, but why is that? Enumerable.Count() needs to iterate over each element and count them. Let's first take a look at Any()
.
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
if (source == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
if (source is ICollection<TSource> collectionoft) return collectionoft.Count != 0;
else if (source is IIListProvider<TSource> listProv)
{
// Note that this check differs from the corresponding check in
// Count (whereas otherwise this method parallels it). If the count
// can't be retrieved cheaply, that likely means we'd need to iterate
// through the entire sequence in order to get the count, and in that
// case, we'll generally be better off falling through to the logic
// below that only enumerates at most a single element.
int count = listProv.GetCount(onlyIfCheap: true);
if (count >= 0) return count != 0;
}
else if (source is ICollection collection) return collection.Count != 0;
using (IEnumerator<TSource> e = source.GetEnumerator())
{
return e.MoveNext();
}
}
Here we can see that there are a lot of conditionals to find the fastest way to determine if there are any elements. The last bit of code is what gets executed in our benchmark. There is a single iteration e.MoveNext()
and a Boolean return. This doesn't depend on the size, it's O(1). Lets now take a look at Count()
.
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;
}
Here we see that the iteration is called for every element while (e.MoveNext())
, a counter incremented, and the count returned. Looking back at the benchmark data and comparing Enumerable_CountGreaterThanZero with N = 1,000 and N = 10,000 the runtime is a direct function of the size, it's O(n).
So if you want to know if there are any elements in an Enumerable, use Any()
. If you use Resharper, it will provide a hint to use Any()
instead of Count() > 0
When it comes to lists, calling count Count
is near instantaneous. Any()
takes longer, however, it's still fast. Both are O(1).
The Any()
method, is the same method that is called on the enumerable since list implements IEnumerable. Referring to the code for Any()
, we can see that it finds the fastest way to check if there are any elements. Since List
is an IColleciton
, it executes .Count != 0
for this check.
Notice that Count
isn't a method, it's a property. In lists, it doesn't need to iterate over every element to determine how many there are of them, but instead lists keep track of how many elements there are and it returns that integer.
// Read-only property describing how many elements are in the List.
public int Count => _size;
The performance difference between Any()
and Count > 0
is the type checking overhead in Any()
. The performance is almost negligible however and it is always advised to avoid premature optimization and to prefer legibility. So unless you are running this in a hot path, calling it very heavily, Any()
would be preferred.
The benchmark source code used for this comparison is included in it's entirety here.
LinqAnyBenchmark.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
</ItemGroup>
</Project>
Benchmarks.cs
namespace LinqAnyBenchmark
{
using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
[MemoryDiagnoser]
public class Benchmark
{
private IEnumerable<Guid> enumerableGuids;
private List<Guid> listGuids;
[Params(1000, 10000)]
public int N;
[GlobalSetup]
public void Setup()
{
enumerableGuids =
Enumerable
.Range(0, N)
.Select(_ => Guid.NewGuid());
listGuids =
Enumerable
.Range(0, N)
.Select(_ => Guid.NewGuid())
.ToList();
}
[Benchmark]
public bool Enumerable_Any() => enumerableGuids.Any();
[Benchmark]
public bool Enumerable_CountGreaterThanZero() => enumerableGuids.Count() > 0;
[Benchmark]
public bool List_Any() => listGuids.Any();
[Benchmark]
public bool List_CountGreaterThanZero() => listGuids.Count > 0;
}
}
Program.cs
namespace LinqAnyBenchmark
{
using BenchmarkDotNet.Running;
class Program
{
static void Main(string[] args) => BenchmarkRunner.Run<Benchmark>();
}
}