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:

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 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