## Why is this an issue?

Both the `List.Find` method and the `Enumerable.FirstOrDefault` method can be used to locate the first element that meets a specified condition within a collection. However, for `List` objects, `List.Find` may offer superior performance compared to `Enumerable.FirstOrDefault`. While the performance difference might be negligible for small collections, it can become significant for larger collections. This observation also holds true for `ImmutableList` and arrays.

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 `FirstOrDefault` closer to the performance of collection-specific `Find` methods in most scenarios.

Applies to

- List
- Array
- ImmutableList

### What is the potential impact?

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

## How to fix it

The `Find` method is defined on the collection class, and it has the same signature as `FirstOrDefault` extension method. The function can be replaced in place.

### Code examples

#### Noncompliant code example

    int GetValue(List<int> data) =>
        data.FirstOrDefault(x => x % 2 == 0);

    int GetValue(int[] data) =>
        data.FirstOrDefault(x => x % 2 == 0);

#### Compliant solution

    int GetValue(List<int> data) =>
        data.Find(x => x % 2 == 0);

    int GetValue(int[] data) =>
        Array.Find(data, x => x % 2 == 0);

## Resources

### Documentation

- List<T>.Find(Predicate<T>)
- Array.Find<T>(T[], Predicate<T>)
- ImmutableList<T>.Find(Predicate<T>)
- Enumerable.FirstOrDefault(Predicate<T>)

### Benchmarks

Method | Runtime | Categories | Mean | Standard Deviation | Allocated
--- | --- | --- | --- | --- | ---
ArrayFirstOrDefault | .NET 8.0 | Array | 10.515 μs | 0.1410 μs | 32 B
ArrayFind | .NET 8.0 | Array | 4.417 μs | 0.0729 μs | -
ArrayFirstOrDefault | .NET 9.0 | Array | 2.262 μs | 0.0135 μs | -
ArrayFind | .NET 9.0 | Array | 3.428 μs | 0.0206 μs | -
ArrayFirstOrDefault | .NET Framework 4.8.1 | Array | 45.074 μs | 0.7517 μs | 32 B
ArrayFind | .NET Framework 4.8.1 | Array | 13.948 μs | 0.1496 μs | -
ImmutableListFirstOrDefault | .NET 8.0 | ImmutableList<T> | 83.796 μs | 1.3199 μs | 72 B
ImmutableListFind | .NET 8.0 | ImmutableList<T> | 59.720 μs | 1.0723 μs | -
ImmutableListFirstOrDefault | .NET 9.0 | ImmutableList<T> | 81.984 μs | 1.0886 μs | 72 B
ImmutableListFind | .NET 9.0 | ImmutableList<T> | 58.288 μs | 0.8079 μs | -
ImmutableListFirstOrDefault | .NET Framework 4.8.1 | ImmutableList<T> | 446.893 μs | 9.8430 μs | 76 B
ImmutableListFind | .NET Framework 4.8.1 | ImmutableList<T> | 427.476 μs | 3.3371 μs | -
ListFirstOrDefault | .NET 8.0 | List<T> | 14.808 μs | 0.1723 μs | 40 B
ListFind | .NET 8.0 | List<T> | 6.040 μs | 0.1104 μs | -
ListFirstOrDefault | .NET 9.0 | List<T> | 2.233 μs | 0.0154 μs | -
ListFind | .NET 9.0 | List<T> | 4.458 μs | 0.0745 μs | -
ListFirstOrDefault | .NET Framework 4.8.1 | List<T> | 57.290 μs | 1.0494 μs | 40 B
ListFind | .NET Framework 4.8.1 | List<T> | 18.476 μs | 0.0504 μs | -

#### 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;
    private readonly static Predicate<int> ConditionPredicate = static x => x == 1;
    private List<int> list;
    private ImmutableList<int> immutableList;
    private int[] array;
    public const int N = 10_000;

    [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(Baseline = true)]
    public int ListFirstOrDefault() =>
        list.FirstOrDefault(ConditionFunc);

    [BenchmarkCategory("List<T>"), Benchmark]
    public int ListFind() =>
        list.Find(ConditionPredicate);

    [BenchmarkCategory("ImmutableList<T>"), Benchmark(Baseline = true)]
    public int ImmutableListFirstOrDefault() =>
        immutableList.FirstOrDefault(ConditionFunc);

    [BenchmarkCategory("ImmutableList<T>"), Benchmark]
    public int ImmutableListFind() =>
        immutableList.Find(ConditionPredicate);

    [BenchmarkCategory("Array"), Benchmark(Baseline = true)]
    public int ArrayFirstOrDefault() =>
        array.FirstOrDefault(ConditionFunc);

    [BenchmarkCategory("Array"), Benchmark]
    public int ArrayFind() =>
        Array.Find(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