org.sonar.plugins.csharp.S6617.html Maven / Gradle / Ivy
Why is this an issue?
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.
What is the potential impact?
We measured a significant improvement both in execution time and memory allocation. For more details see the Benchmarks
section from
the More info
tab.
Exceptions
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.
How to fix it
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:
Code examples
Noncompliant code example
bool ValueExists(HashSet<int> data) =>
data.Any(x => x == 42);
bool ValueExists(List<int> data) =>
data.Any(x => x == 42);
Compliant solution
bool ValueExists(HashSet<int> data) =>
data.Contains(42);
bool ValueExists(List<int> data) =>
data.Contains(42);
Resources
Documentation
Articles & blog posts
Benchmarks
Method
Runtime
Mean
Standard Deviation
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
-
Glossary
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