Quantcast
Channel: LINQ equivalent of foreach for IEnumerable - Stack Overflow
Viewing all articles
Browse latest Browse all 23

Answer by l33t for LINQ equivalent of foreach for IEnumerable

$
0
0

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

No allocations. No GC

Weekly typed foreach: Memory usage

enter image description here

ForEach extension: Memory usage

Lots of allocations. Heavy GC.

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.


Viewing all articles
Browse latest Browse all 23

Latest Images

Trending Articles





Latest Images