Enumerating an IEnumerable<T>
is 2 to 3 times slower than enumerating the same List<T>
directly. This is due to a subtlety on how C# selects its enumerator for a given type.
List<T>
exposes 3 enumerators:
List<T>.Enumerator List<T>.GetEnumerator()
IEnumerator<T> IEnumerable<T>.GetEnumerator()
IEnumerator IEnumerable.GetEnumerator()
When C# compiles a foreach
loop, it will select the enumerator in the above order. Note that a type doesn't need to implement IEnumerable
or IEnumerable<T>
to be enumerable, it just needs a method named GetEnumerator()
that returns an enumerator.
Now, List<T>.GetEnumerator()
has the advantage of being statically typed which makes all calls to List<T>.Enumerator.get_Current
and List<T>.Enumerator.MoveNext()
static-bound instead of virtual.
10M iterations (coreclr):
for(int i ...) 73 ms
foreach(... List<T>) 215 ms
foreach(... IEnumerable<T>) 698 ms
foreach(... IEnumerable) 1028 ms
for(int *p ...) 50 ms
10M iterations (Framework):
for(int i ...) 210 ms
foreach(... List<T>) 252 ms
foreach(... IEnumerable<T>) 537 ms
foreach(... IEnumerable) 844 ms
for(int *p ...) 202 ms
Disclaimer
I should point out the actual iteration in a list is rarely the bottleneck. Keep in mind those are hundreds of milliseconds over millions of iterations. Any work in the loop more complicated than a few arithmetic operations will be overwhelmingly costlier than the iteration itself.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…