So many answers, yet ALL fail to pinpoint one very significant problem with a custom genericForEach
extension: Performance! And more specifically, memory usage and GC.
Consider the sample below. Targeting .NET Framework 4.7.2
, configuration is Release
and platform is Any CPU
.
public static class Enumerables{ public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action) { foreach (T item in @this) { action(item); } }}class Program{ private static void NoOp(int value) {} static void Main(string[] args) { var list = Enumerable.Range(0, 10).ToList(); for (int i = 0; i < 1000000; i++) { // WithLinq(list); // WithoutLinqNoGood(list); WithoutLinq(list); } } private static void WithoutLinq(List<int> list) { foreach (var item in list) { NoOp(item); } } private static void WithLinq(IEnumerable<int> list) => list.ForEach(NoOp); private static void WithoutLinqNoGood(IEnumerable<int> enumerable) { foreach (var item in enumerable) { NoOp(item); } }}
At a first glance, all three variants should perform equally well. However, when the ForEach
extension method is called many, many times, you will end up with garbage that implies a costly GC. In fact, having this ForEach
extension method on a hot path has been proven to totally kill performance in our loop-intensive application.
Similarly, the weekly typed foreach
loop will also produce garbage, but it will still be faster and less memory-intensive than the ForEach
extension (which also suffers from a delegate allocation).
Strongly typed foreach: Memory usage
Weekly typed foreach: Memory usage
ForEach extension: Memory usage
Analysis
For a strongly typed foreach
the compiler is able to use any optimized enumerator (e.g. value based) of a class, whereas a generic ForEach
extension must fall back to a generic enumerator which will be allocated on each run. Furthermore, the actual delegate will also imply an additional allocation.
You would get similar bad results with the WithoutLinqNoGood
method. There, the argument is of type IEnumerable<int>
instead of List<int>
implying the same type of enumerator allocation.
Below are the relevant differences in IL
. A value based enumerator is certainly preferable!
IL_0001: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<!!T>::GetEnumerator()
vs
IL_0001: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
Conclusion
The OP asked how to call ForEach()
on an IEnumerable<T>
. The original answer clearly shows how it can be done. Sure you can do it, but then again; my answer clearly shows that you shouldn't.