This code example shows the different behavior of runs of IEnumerable and List.
This example has no claim to how lists are handled in the very very best case, but how they are currently handled in everyday life.
There are always the discussions whether to use List or IEnumerable - especially for returns. So you can find many repository examples in the net, which say that IEnumerable is always the best solution - although for example a materialization is already done.
Whats better
// This
public IEnumerable<MyEntity> GetEntities() // return List as IEnumerable
return _context.MyEntities.ToList();
// or
public List<MyEntity> GetEntities()
return _context.MyEntities.ToList(); // return List as List
BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5131/22H2/2022Update)
AMD Ryzen 9 9950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 9.0.100
[Host] : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
.NET 7.0 : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.11 (8.0.1124.51707), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
.NET 9.0 : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
| Method | Runtime | Mean | Error | StdDev | Ratio | Gen0 | Allocated |
|------------------------ |--------- |-----------:|---------:|---------:|------:|-------:|----------:|
| IEnumerable | .NET 7.0 | 2,051.0 ns | 10.50 ns | 9.82 ns | 4.18 | - | 40 B |
| IEnumerable | .NET 8.0 | 378.1 ns | 0.94 ns | 0.83 ns | 0.77 | 0.0024 | 40 B |
| IEnumerable | .NET 9.0 | 491.6 ns | 9.80 ns | 24.76 ns | 1.00 | 0.0019 | 40 B |
| | | | | | | | |
| IEnumerable_ToList | .NET 7.0 | 729.2 ns | 4.05 ns | 3.79 ns | 2.03 | 0.2422 | 4056 B |
| IEnumerable_ToList | .NET 8.0 | 343.0 ns | 3.64 ns | 3.22 ns | 0.96 | 0.2422 | 4056 B |
| IEnumerable_ToList | .NET 9.0 | 358.7 ns | 4.86 ns | 4.55 ns | 1.00 | 0.2422 | 4056 B |
| | | | | | | | |
| List | .NET 7.0 | 258.4 ns | 4.21 ns | 3.94 ns | 1.22 | - | - |
| List | .NET 8.0 | 254.6 ns | 4.78 ns | 4.47 ns | 1.20 | - | - |
| List | .NET 9.0 | 212.1 ns | 2.65 ns | 2.35 ns | 1.00 | - | - |
| | | | | | | | |
| List_IEnumerable | .NET 7.0 | 3,142.4 ns | 8.06 ns | 7.54 ns | 4.24 | - | 40 B |
| List_IEnumerable | .NET 8.0 | 692.4 ns | 1.67 ns | 1.30 ns | 0.93 | 0.0019 | 40 B |
| List_IEnumerable | .NET 9.0 | 741.7 ns | 1.39 ns | 1.09 ns | 1.00 | 0.0019 | 40 B |
| | | | | | | | |
| List_IEnumerable_ToList | .NET 7.0 | 345.6 ns | 3.60 ns | 3.19 ns | 1.05 | 0.2422 | 4056 B |
| List_IEnumerable_ToList | .NET 8.0 | 365.9 ns | 5.52 ns | 4.61 ns | 1.11 | 0.2422 | 4056 B |
| List_IEnumerable_ToList | .NET 9.0 | 328.7 ns | 5.57 ns | 4.94 ns | 1.00 | 0.2422 | 4056 B |
- If you iterate through IEnumerable it means that the enumerator must be passed through when looping. This costs time.
- In this example, ToList ensures that materialization takes place before the loop, which is why appropriate allocations are necessary, but the throughput becomes faster.
- By far the fastest way is to treat an already materialized
List
. - The slowest way is to treat a
List
as IEnumerable, so that the enumerator has to be run again. - While List behavior has changed virtually nothing in .NET 8, all IEnumerable cases are significantly faster in .NET 8 than in .NET 7.
- Performance has been improved even further with .NET 9.
- Treat a materialized
List
as aList
- The return of a
List
should therefore not beIEnumerable
if possible - A
ToList
on an already materialized list hiding behindIEnumerable
creates unnecessary allocations.
Don't forget: this is not about the best way of
IEnumerable
andList
in general, but how they are used in everyday life today and what impact this has.IEnumerable
is good, has its legitimacy and advantages!
dotnet run -c Release --framework net9.0
- 2023/11 - Add .NET 8
- 2024/11 - Add .NET 9