When testing if a collection contains a specific item by simple equality, both ICollection.Contains(T item) and
IEnumerable.Any(x ⇒ x == item) can be used. However, Any searches the data structure in a linear manner using a foreach
loop, whereas Contains is considerably faster in some collection types, because of the underlying implementation. More specifically:
HashSet<T> is a hashtable, and therefore has an O(1) lookup SortedSet<T> is a red-black tree, and therefore has a O(logN) lookup List<T> is a linear search, and therefore has an O(N) lookup, but the EqualityComparer is optimized for the T
type, which is not the case for Any For small collections, the performance difference may be negligible, but for large collections, it can be noticeable.
We measured a significant improvement both in execution time and memory allocation. For more details see the Benchmarks section from
the More info tab.
Since LINQ to
Entities relies a lot on System.Linq for query conversion,
this rule won’t raise when used within LINQ to Entities syntaxes.
Contains is a method defined on the ICollection<T> interface and takes a T item argument.
Any is an extension method defined on the IEnumerable<T> interface and takes a predicate argument. Therefore, calls
with simple equality checks like Any(x ⇒ x == item) can be replaced by Contains(item).
This applies to the following collection types:
Function ValueExists(data As HashSet(Of Integer)) As Boolean
Return data.Any(Function(x) x = 42)
End Function
Function ValueExists(data As List(Of Integer)) As Boolean
Return data.Any(Function(x) x = 42)
End Function
Function ValueExists(data As HashSet(Of Integer)) As Boolean
Return data.Contains(42)
End Function
Function ValueExists(data As List(Of Integer)) As Boolean
Return data.Contains(42)
End Function
| Method | Runtime | Mean | StdDev | Allocated |
|---|---|---|---|---|
HashSet_Any |
.NET 7.0 |
35,388.333 us |
620.1863 us |
40132 B |
HashSet_Contains |
.NET 7.0 |
3.799 us |
0.1489 us |
- |
List_Any |
.NET 7.0 |
32,851.509 us |
667.1658 us |
40130 B |
List_Contains |
.NET 7.0 |
375.132 us |
8.0764 us |
- |
HashSet_Any |
.NET Framework 4.6.2 |
28,979.763 us |
678.0093 us |
40448 B |
HashSet_Contains |
.NET Framework 4.6.2 |
5.987 us |
0.1090 us |
- |
List_Any |
.NET Framework 4.6.2 |
25,830.221 us |
487.2470 us |
40448 B |
List_Contains |
.NET Framework 4.6.2 |
5,935.812 us |
57.7569 us |
- |
The results were generated by running the following snippet with BenchmarkDotNet:
[Params(10_000)]
public int SampleSize;
[Params(1_000)]
public int Iterations;
private static HashSet<int> hashSet;
private static List<int> list;
[GlobalSetup]
public void Setup()
{
hashSet = new HashSet<int>(Enumerable.Range(0, SampleSize));
list = Enumerable.Range(0, SampleSize).ToList();
}
[Benchmark]
public void HashSet_Any() =>
CheckAny(hashSet, SampleSize / 2);
[Benchmark]
public void HashSet_Contains() =>
CheckContains(hashSet, SampleSize / 2);
[Benchmark]
public void List_Any() =>
CheckAny(list, SampleSize / 2);
[Benchmark]
public void List_Contains() =>
CheckContains(list, SampleSize / 2);
void CheckAny(IEnumerable<int> values, int target)
{
for (int i = 0; i < Iterations; i++)
{
_ = values.Any(x => x == target); // Enumerable.Any
}
}
void CheckContains(ICollection<int> values, int target)
{
for (int i = 0; i < Iterations; i++)
{
_ = values.Contains(target); // ICollection<T>.Contains
}
}
Hardware configuration:
BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update) 11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores .NET SDK=7.0.203 [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 .NET 7.0 : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 .NET Framework 4.6.2 : .NET Framework 4.8.1 (4.8.9139.0), X64 RyuJIT VectorSize=256