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
We measured at least 2x improvement in the execution time. For more details see the Benchmarks section from the More info
tab.
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.
int GetValue(List<int> data) =>
data.FirstOrDefault(x => x % 2 == 0);
int GetValue(int[] data) =>
data.FirstOrDefault(x => x % 2 == 0);
int GetValue(List<int> data) =>
data.Find(x => x % 2 == 0);
int GetValue(int[] data) =>
Array.Find(data, x => x % 2 == 0);
| 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 |
- |
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