LINQ equivalent of foreach for IEnumerable

后端 未结 22 2155
夕颜
夕颜 2020-11-21 22:54

I\'d like to do the equivalent of the following in LINQ, but I can\'t figure out how:

IEnumerable items = GetItems();
items.ForEach(i => i.DoS         


        
相关标签:
22条回答
  • 2020-11-21 23:30

    Fredrik has provided the fix, but it may be worth considering why this isn't in the framework to start with. I believe the idea is that the LINQ query operators should be side-effect-free, fitting in with a reasonably functional way of looking at the world. Clearly ForEach is exactly the opposite - a purely side-effect-based construct.

    That's not to say this is a bad thing to do - just thinking about the philosophical reasons behind the decision.

    0 讨论(0)
  • 2020-11-21 23:30

    There is an experimental release by Microsoft of Interactive Extensions to LINQ (also on NuGet, see RxTeams's profile for more links). The Channel 9 video explains it well.

    Its docs are only provided in XML format. I have run this documentation in Sandcastle to allow it to be in a more readable format. Unzip the docs archive and look for index.html.

    Among many other goodies, it provides the expected ForEach implementation. It allows you to write code like this:

    int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };
    
    numbers.ForEach(x => Console.WriteLine(x*x));
    
    0 讨论(0)
  • 2020-11-21 23:30

    This "functional approach" abstraction leaks big time. Nothing on the language level prevents side effects. As long as you can make it call your lambda/delegate for every element in the container - you will get the "ForEach" behavior.

    Here for example one way of merging srcDictionary into destDictionary (if key already exists - overwrites)

    this is a hack, and should not be used in any production code.

    var b = srcDictionary.Select(
                                 x=>
                                    {
                                      destDictionary[x.Key] = x.Value;
                                      return true;
                                    }
                                 ).Count();
    
    0 讨论(0)
  • 2020-11-21 23:31

    There is no ForEach extension for IEnumerable; only for List<T>. So you could do

    items.ToList().ForEach(i => i.DoStuff());
    

    Alternatively, write your own ForEach extension method:

    public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
    {
        foreach(T item in enumeration)
        {
            action(item);
        }
    }
    
    0 讨论(0)
  • 2020-11-21 23:31

    So many answers, yet ALL fail to pinpoint one very significant problem with a custom generic ForEach extension: Performance! And more specifically, memory usage and GC.

    Consider the sample below. Targeting .NET Framework 4.7.2 or .NET Core 3.1.401, 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.

    Verified the same behavior when targeting .NET Core 3.1.401 (compiling with Visual Studio 16.7.2).

    0 讨论(0)
  • 2020-11-21 23:32

    Keep your Side Effects out of my IEnumerable

    I'd like to do the equivalent of the following in LINQ, but I can't figure out how:

    As others have pointed out here and abroad LINQ and IEnumerable methods are expected to be side-effect free.

    Do you really want to "do something" to each item in the IEnumerable? Then foreach is the best choice. People aren't surprised when side-effects happen here.

    foreach (var i in items) i.DoStuff();
    

    I bet you don't want a side-effect

    However in my experience side-effects are usually not required. More often than not there is a simple LINQ query waiting to be discovered accompanied by a StackOverflow.com answer by either Jon Skeet, Eric Lippert, or Marc Gravell explaining how to do what you want!

    Some examples

    If you are actually just aggregating (accumulating) some value then you should consider the Aggregate extension method.

    items.Aggregate(initial, (acc, x) => ComputeAccumulatedValue(acc, x));
    

    Perhaps you want to create a new IEnumerable from the existing values.

    items.Select(x => Transform(x));
    

    Or maybe you want to create a look-up table:

    items.ToLookup(x, x => GetTheKey(x))
    

    The list (pun not entirely intended) of possibilities goes on and on.

    0 讨论(0)
提交回复
热议问题