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

- HashSet<T>
- SortedSet<T>
- List<T>

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

- HashSet<T>.Contains(T)
- SortedSet<T>.Contains(T)
- List.Contains(T)
- Enumerable.Any
- LINQ to Entities

### Articles & blog posts

- Collections and Data Structures

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

- Mean
- Standard Deviation
- Allocated

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