我想在LINQ中执行以下等效操作,但我无法确定如何执行:

IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());

真正的语法是什么?


当前回答

ForEach的目的是造成副作用。IEnumerable用于集合的惰性枚举。

当你考虑到这一点时,这个概念上的差异是非常明显的。

SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));

在您对其执行“计数”或“ToList()”或其他操作之前,这不会执行。这显然不是所表达的。

您应该使用IEnumerable扩展来设置迭代链,根据其各自的源和条件定义内容。表达树是强大而高效的,但你应该学会欣赏它们的本质。而且不仅仅是为了围绕它们进行编程,以节省几个字符而忽略惰性求值。

其他回答

现在我们可以选择。。。

        ParallelOptions parallelOptions = new ParallelOptions();
        parallelOptions.MaxDegreeOfParallelism = 4;
#if DEBUG
        parallelOptions.MaxDegreeOfParallelism = 1;
#endif
        Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));

当然,这打开了一个全新的线虫罐头。

ps(对不起,字体是系统决定的)

让你的副作用远离我的IEnumerable

我想在LINQ中执行以下等效操作,但我无法确定如何执行:

正如其他人在国内外指出的,LINQ和IEnumerable方法预计不会产生副作用。

你真的想对IEnumerable中的每个项目“做点什么”吗?那么foreach是最好的选择。当这里出现副作用时,人们并不感到惊讶。

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

我打赌你不想有副作用

然而,根据我的经验,通常不需要副作用。通常情况下,有一个简单的LINQ查询等待被发现,并伴随着Jon Skeet、Eric Lippert或Marc Gravell的StackOverflow.com回答,解释如何做您想要的事情!

一些示例

如果您实际上只是在聚合(累积)一些值,那么应该考虑聚合扩展方法。

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

也许您想从现有值创建一个新的IEnumerable。

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

或者你想创建一个查找表:

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

可能性的列表(双关语并非完全有意)还在继续。

ForEach的目的是造成副作用。IEnumerable用于集合的惰性枚举。

当你考虑到这一点时,这个概念上的差异是非常明显的。

SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));

在您对其执行“计数”或“ToList()”或其他操作之前,这不会执行。这显然不是所表达的。

您应该使用IEnumerable扩展来设置迭代链,根据其各自的源和条件定义内容。表达树是强大而高效的,但你应该学会欣赏它们的本质。而且不仅仅是为了围绕它们进行编程,以节省几个字符而忽略惰性求值。

这种“功能方法”抽象泄露了大量时间。在语言层面上没有任何东西可以防止副作用。只要你能让它为容器中的每个元素调用lambda/委托,你就会得到“ForEach”行为。

例如,这里有一种将srcDictionary合并到destDictionary的方法(如果键已经存在-重写)

这是一个黑客,不应该在任何生产代码中使用。

var b = srcDictionary.Select(
                             x=>
                                {
                                  destDictionary[x.Key] = x.Value;
                                  return true;
                                }
                             ).Count();

这么多的答案,但所有人都未能找到自定义通用ForEach扩展的一个非常重要的问题:性能!更具体地说,内存使用和GC。

考虑以下示例。针对.NET Framework 4.7.2或.NET Core 3.1.401,配置为Release,平台为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);
        }
    }
}

乍一看,这三种变体都应该表现得同样出色。然而,当ForEach扩展方法被多次调用时,最终会产生垃圾,这意味着代价高昂的GC。事实上,在热路径上使用此ForEach扩展方法已被证明会完全破坏循环密集型应用程序的性能。

类似地,弱类型的foreach循环也会产生垃圾,但它仍然比foreach扩展更快,占用的内存更少(它也会受到委托分配的影响)。

强类型foreach:内存使用

弱类型foreach:内存使用

ForEach扩展:内存使用

分析

对于强类型foreach,编译器可以使用类的任何优化枚举器(例如基于值的),而泛型foreach扩展必须返回到将在每次运行时分配的泛型枚举器。此外,实际代表还将意味着额外分配。

使用WithoutLinqNoGood方法也会得到类似的坏结果。在那里,参数的类型是IEnumerable<int>,而不是List<int>。这意味着相同类型的枚举器分配。

以下是IL中的相关差异。基于值的枚举器当然更可取!

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()

结论

OP询问如何在IEnumerable<T>上调用ForEach()。最初的答案清楚地表明了如何做到这一点。当然你能做到,但再说一遍;我的回答清楚地表明你不应该。

在针对.NET Core 3.1.401(使用Visual Studio 16.7.2编译)时验证了相同的行为。