## Why is this an issue?

Both the `List.Exists` method and `IEnumerable.Any` method can be used to find the first element that satisfies a predicate in a collection. However, `List.Exists` can be faster than `IEnumerable.Any` for `List` objects, as well as requires significantly less memory. For small collections, the performance difference may be negligible, but for large collections, it can be noticeable. The same applies to `ImmutableList` and arrays too.

It is important to enable this rule with caution, as performance outcomes can vary significantly across different runtimes. Notably, the performance improvements in .NET 9 have brought `Any` closer to the performance of collection-specific `Exists` methods in most scenarios.

Applies to

- List
- Array
- ImmutableList

### What is the potential impact?

We measured at least 3x improvement in execution time. For more details see the `Benchmarks` section from the `More info` tab.

Also, no memory allocations were needed for the `Exists` method, since the search is done in-place.

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

The `Exists` method is defined on the collection class, and it has the same signature as `Any` extension method if a predicate is used. The method can be replaced in place.

### Code examples

#### Noncompliant code example

    bool ContainsEven(List<int> data) =>
        data.Any(x => x % 2 == 0);

    bool ContainsEven(int[] data) =>
        data.Any(x => x % 2 == 0);

#### Compliant solution

    bool ContainsEven(List<int> data) =>
        data.Exists(x => x % 2 == 0);

    bool ContainsEven(int[] data) =>
        Array.Exists(data, x => x % 2 == 0);

## Resources

### Documentation

- Microsoft Learn - List<T>.Exists(Predicate<T>)
- Microsoft Learn - Array.Exists<T>(T[], Predicate<T>)
- Microsoft Learn - ImmutableList<T>.Exists(Predicate<T>)
- Microsoft Learn - Enumerable.Any(Predicate<T>)
- Microsoft Learn - LINQ to Entities

### Benchmarks

Method | Runtime | Categories | Mean | Standard Deviation | Allocated
--- | --- | --- | --- | --- | ---
ArrayAny | .NET 8.0 | Array | 1,174.0 ns | 16.44 ns | 32 B
ArrayExists | .NET 8.0 | Array | 570.6 ns | 7.12 ns | -
ArrayAny | .NET 9.0 | Array | 358.5 ns | 5.57 ns | -
ArrayExists | .NET 9.0 | Array | 581.6 ns | 6.17 ns | -
ArrayAny | .NET Framework 4.8.1 | Array | 4,896.0 ns | 102.83 ns | 32 B
ArrayExists | .NET Framework 4.8.1 | Array | 1,649.4 ns | 29.81 ns | -
ImmutableListAny | .NET 8.0 | ImmutableList<T> | 7,859.3 ns | 91.45 ns | 72 B
ImmutableListExists | .NET 8.0 | ImmutableList<T> | 5,898.1 ns | 81.69 ns | -
ImmutableListAny | .NET 9.0 | ImmutableList<T> | 7,748.9 ns | 119.10 ns | 72 B
ImmutableListExists | .NET 9.0 | ImmutableList<T> | 5,705.0 ns | 31.53 ns | -
ImmutableListAny | .NET Framework 4.8.1 | ImmutableList<T> | 45,118.5 ns | 168.72 ns | 72 B
ImmutableListExists | .NET Framework 4.8.1 | ImmutableList<T> | 41,966.0 ns | 631.59 ns | -
ListAny | .NET 8.0 | List<T> | 1,643.5 ns | 13.09 ns | 40 B
ListExists | .NET 8.0 | List<T> | 726.2 ns | 11.99 ns | -
ListAny | .NET 9.0 | List<T> | 398.6 ns | 8.20 ns | -
ListExists | .NET 9.0 | List<T> | 612.4 ns | 18.73 ns | -
ListAny | .NET Framework 4.8.1 | List<T> | 5,621.5 ns | 35.80 ns | 40 B
ListExists | .NET Framework 4.8.1 | List<T> | 1,748.0 ns | 11.76 ns | -

#### Glossary

- Mean
- Standard Deviation
- Allocated

The results were generated by running the following snippet with BenchmarkDotNet:

    // Explicitly cache the delegates to avoid allocations inside the benchmark.
    private readonly static Func<int, bool> ConditionFunc = static x => x == -1 * Math.Abs(x);
    private readonly static Predicate<int> ConditionPredicate = static x => x == -1 * Math.Abs(x);

    private List<int> list;
    private ImmutableList<int> immutableList;
    private int[] array;

    [Params(1_000)]
    public int N { get; set; }

    [GlobalSetup]
    public void GlobalSetup()
    {
        list = Enumerable.Range(0, N).Select(x => N - x).ToList();
        immutableList = ImmutableList.CreateRange(list);
        array = list.ToArray();
    }

    [BenchmarkCategory("List<T>"), Benchmark]
    public bool ListAny() =>
        list.Any(ConditionFunc);

    [BenchmarkCategory("List<T>"), Benchmark(Baseline = true)]
    public bool ListExists() =>
        list.Exists(ConditionPredicate);

    [BenchmarkCategory("ImmutableList<T>"), Benchmark(Baseline = true)]
    public bool ImmutableListAny() =>
        immutableList.Any(ConditionFunc);

    [BenchmarkCategory("ImmutableList<T>"), Benchmark]
    public bool ImmutableListExists() =>
        immutableList.Exists(ConditionPredicate);

    [BenchmarkCategory("Array"), Benchmark(Baseline = true)]
    public bool ArrayAny() =>
        array.Any(ConditionFunc);

    [BenchmarkCategory("Array"), Benchmark]
    public bool ArrayExists() =>
        Array.Exists(array, ConditionPredicate);

Hardware configuration:

    BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3)
    11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
      [Host]               : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
      .NET 8.0             : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
      .NET 9.0             : .NET 9.0.0 (9.0.24.47305), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
      .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256