If we have a List<T>
and an IList<T>
, enumerating over the List<T>
is faster than enumerating over the IList<T>
. Why is that?
Benchmark Code
[MemoryDiagnoser]
public class Benchmark
{
private readonly List<int> numbersList = Enumerable.Range(0, 10_000).ToList();
private readonly IList<int> numbersIList = Enumerable.Range(0, 10_000).ToList();
[Benchmark(Baseline = true)]
public int GetSumOfList()
{
var sum = 0;
foreach (var number in numbersList) { sum += number; }
return sum;
}
[Benchmark]
public int GetSumOfIList()
{
var sum = 0;
foreach (var number in numbersIList) { sum += number; }
return sum;
}
}
We have a result like this:
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|-------------- |----------:|----------:|----------:|------:|--------:|----------:|------------:|
| GetSumOfList | 4.214 us | 0.0836 us | 0.0782 us | 1.00 | 0.03 | - | NA |
| GetSumOfIList | 19.508 us | 0.0459 us | 0.0384 us | 4.63 | 0.08 | 40 B | NA |
40 bytes
Were are those 40 bytes coming from? When you call List<T>.GetEnumerator()
(which will be done in every foreach
loop) you get a struct named Enumerator. When calling IList<T>.GetEnumerator()
you get a variable of type IEnumerator
Virtual method calls
But there is a second dimension to this. When you call IList<T>.GetEnumerator()
, it has to call a virtual method, which is slower than calling a non-virtual method. Basically, someone has to check which concrete type is really being used, and then call the correct method. This is not the case with List<T>
, which is already the concrete type.