LINQ相当于foren for IEnumerable <T>

tag*_*s2k 694 linq ienumerable foreach

我想在LINQ中做相同的以下内容,但我无法弄清楚如何:

IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());
Run Code Online (Sandbox Code Playgroud)

什么是真正的语法?

Fre*_*eth 829

没有ForEach扩展名IEnumerable; 仅限于List<T>.所以你可以做到

items.ToList().ForEach(i => i.DoStuff());
Run Code Online (Sandbox Code Playgroud)

或者,编写自己的ForEach扩展方法:

public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach(T item in enumeration)
    {
        action(item);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 注意ToList(),因为ToList()会创建序列的副本,这可能会导致性能和内存问题. (203认同)
  • 请注意,在第一种情况下,开发人员1)几乎不保存打字,2)不必要地分配内存以将整个序列作为列表存储在丢弃之前.在LINQ之前想一想 (16认同)
  • ".Foreach()"会更好的原因很少.1.多线程,可由PLINQ完成.2.例外情况,您可能希望在循环中收集所有异常并立即抛出它; 它们是噪音代码. (15认同)
  • PLINQ使用ForAll而不是ForEach,这就是为什么你不使用ForEach. (12认同)
  • 你应该在扩展方法中返回一个`IENumerable <T>`.例如:`public static IEnumerable <T> ForAll <T>(此IEnumerable <T>计算,Action <T>动作){foreach(枚举中的T项){action(item); } return enumeration; }` (9认同)
  • 第一个例子很糟糕,但我认为第二个例子对于在适当的情况下使用时的可读性肯定是有用的.特别是在你只能传递方法的情况下,不使用lambda表示法.例如:`sequence.ForEach(doSomething);`而不是:`foreach(序列中的项目项)doSomething(item);`如果你有很多可以简化的东西,这可以产生巨大的差异. (4认同)
  • 注意 - 由于ToList执行的副本,这对于您要执行改变IEnumerable中每个项的状态的操作的情况不起作用. (3认同)
  • 更清晰和可读的方法是使用与ToList().ForEach()相对的扩展方法.虽然后者是一个快速修复,但ToList()部分没有任何关于代码功能的值.它仅仅作为适配器位于中间,这使得代码看起来更加混乱. (2认同)

Jon*_*eet 355

Fredrik提供了修复程序,但值得考虑为什么这不是在框架中开始.我相信这个想法是LINQ查询运算符应该是无副作用的,适合用于查看世界的合理功能的方式.显然ForEach恰恰相反 - 纯粹的基于副作用的构造.

这并不是说这是一件坏事 - 只考虑决定背后的哲学原因.

  • 然而,F#有Seq.iter (42认同)
  • 正是Eric Lippert在http://blogs.msdn.com/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx中所说的内容 (28认同)
  • @Stephen Swensen:是的,F#有Seq.iter,但F#也有不可变的数据结构.当在这些上使用Seq.iter时,原始的Seq不会被修改; 返回带有副作用元素的新Seq. (11认同)
  • @Anders - "Seq.iter"不返回任何东西,更不用说带有副作用元素的新seq了.这真的只是副作用.您可能会想到`Seq.map`,因为`Seq.iter`正好等同于`IEnumerabe <_>`上的`ForEach`扩展方法. (7认同)
  • 但是,LINQ函数通常提供的一个非常有用的东西是foreach()没有 - Linq函数采用的匿名函数也可以将索引作为参数,而对于foreach,你必须重建经典for(int i ..构造.函数式语言必须允许具有副作用的迭代 - 否则你永远无法用它们做任何工作:)我想指出典型的函数方法是返回原始枚举不变. (6认同)
  • @Walt:我仍然不确定我是否同意,部分是因为典型的真正功能性方法不会像这样使用*foreach ......它会使用更像LINQ的函数式方法,创建一个新的序列. (3认同)
  • 作为参考,这个问题提出了同样的问题,并从"权力"得到输入:http://stackoverflow.com/questions/858978/lambda-expression-using-foreach-clause (2认同)
  • @Walt:不过,实用性不需要*无处不在*。通常我会在无副作用部分使用 LINQ,然后在代码的副作用部分使用 `foreach` 循环。如果你想要 `foreach` 中的索引,你可以使用 `foreach (var pair in sequence.Select((value, index) =&gt; new { value, index }))` 或者使用我的来自 MiscUtil 的 SmartEnumerable 类相同:http://www.yoda.arachsys.com/csharp/miscutil/usage/smartenumerable.html (2认同)
  • @drstevens:说实话,我认为即便如此,我仍然可能会使用`foreach`. (2认同)
  • 我阅读了埃里克·利珀特(Eric Lippert)的文章,并阅读了有关此问答的评论,但仍然不明白为什么`IList.ForEach`会产生副作用,而`IEnumerable.ForEach`不会引起副作用-效果。我同意反对“ ForEach”的所有其他论点,但是“ ForEach”将允许使用流畅的API。有人可以解释为什么副作用对“ IEnumerable”不利,而对“ IList”不利吗? (2认同)
  • @l33t:我认为“灾难性”是一个“巨大的”夸大,你很可能会发现当你在循环中实际“做”某件事时,性能差异变得微不足道。我并不特别关心“什么都不做”需要花多少时间。 (2认同)

drs*_*ens 38

更新 7/17/2012:显然从C#5.0开始,foreach下面描述的行为已经改变," 在嵌套的lambda表达式中使用foreach迭代变量不再产生意外结果. "这个答案不适用于C#≥5.0 .

@John Skeet和所有喜欢foreach关键字的人.

5.0之前的 C#中"foreach"的问题在于,它与等效的"for comprehension"在其他语言中的工作方式不一致,以及我期望它如何工作(这里所说的个人意见只是因为其他人提到了他们的关于可读性的意见).查看有关" 访问修改后的闭包 "以及" 关闭环路变量被认为有害 "的所有问题.这只是"有害的",因为在C#中实现了"foreach"的方式.

使用功能等效的扩展方法与@Fredrik Kalseth的答案中的示例一起使用以下示例.

public static class Enumerables
{
    public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
    {
        foreach (T item in @this)
        {
            action(item);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

为过度设计的例子道歉.我只使用Observable,因为做这样的事情并不是完全不可取的.显然有更好的方法来创建这个可观察的,我只是试图证明一个观点.通常,订阅observable的代码是异步执行的,并且可能在另一个线程中执行.如果使用"foreach",这可能会产生非常奇怪且可能不确定的结果.

使用"ForEach"扩展方法的以下测试通过:

[Test]
public void ForEachExtensionWin()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                values.ForEach(value => 
                                    source.OnNext(() => value));

                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Win
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}
Run Code Online (Sandbox Code Playgroud)

以下失败并显示错误:

预期:相当于<0,1,2,3,4,5,6,7,8,9>但是:<9,9,9,9,9,9,9,9,9,9>

[Test]
public void ForEachKeywordFail()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                foreach (var value in values)
                                {
                                    //If you have resharper, notice the warning
                                    source.OnNext(() => value);
                                }
                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Fail
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}
Run Code Online (Sandbox Code Playgroud)


小智 32

您可以使用FirstOrDefault()可用的扩展程序IEnumerable<T>.通过false从谓词返回,它将针对每个元素运行,但不会关心它实际上没有找到匹配.这样可以避免ToList()开销.

IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });
Run Code Online (Sandbox Code Playgroud)

  • 这是一个聪明的伎俩,但请不要写这样的代码. (225认同)
  • 我不得不downvote,因为虽然_technically_正确,你未来的自我和所有的继承人将永远恨你,因为而不是'foreach(项目i in GetItems()){i.DoStuff();}`你拿了更多的角色并使它变得非常扑朔迷离 (22认同)
  • 好吧,但是更多可读的hack:`items.All(i => {i.DoStuff(); return true;}` (14认同)
  • 我也同意.它可能是一个聪明的小黑客,但乍一看,这个代码不清楚它在做什么,当然与标准的foreach循环相比. (9认同)
  • 我知道这是一个相当古老的帖子,但我也不得不将其投票.我同意这是一个聪明的伎俩,但它不可维护.乍一看,它似乎只对列表中的第一项做了些什么.这可能很容易导致未来更多的错误,因为编码人员误解了它的功能和它应该做的事情. (5认同)
  • 这是副作用编程和恕我直言劝阻. (4认同)
  • 更好地创建自己的扩展,因为这个"黑客"太丑了,只需要额外的CPU成本.要客观:这是一个糟糕的决定. (2认同)
  • @Don - 我同意扩展方法更清晰。但考虑到这一点,为什么人们会使用这种令人困惑的 FirstOrDefault 技巧来实现它,而不是 Fredrik 直接的“foreach”实现呢? (2认同)

Dor*_*man 21

我采用了Fredrik的方法并修改了返回类型.

这样,该方法支持延迟执行,就像其他LINQ方法一样.

编辑:如果不清楚,此方法的任何使用必须以ToList()或任何其他方式结束,以强制该方法处理完整的可枚举.否则,将不会执行该操作!

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach (T item in enumeration)
    {
        action(item);
        yield return item;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是帮助看到它的测试:

[Test]
public void TestDefferedExecutionOfIEnumerableForEach()
{
    IEnumerable<char> enumerable = new[] {'a', 'b', 'c'};

    var sb = new StringBuilder();

    enumerable
        .ForEach(c => sb.Append("1"))
        .ForEach(c => sb.Append("2"))
        .ToList();

    Assert.That(sb.ToString(), Is.EqualTo("121212"));
}
Run Code Online (Sandbox Code Playgroud)

如果最后删除ToList(),您将看到测试失败,因为StringBuilder包含空字符串.这是因为没有方法强制ForEach枚举.

  • 这与使用`ToList`调用`Select`完全相同.`ForEach`的目的是不必调用`ToList`.它应该立即执行. (3认同)
  • 将执行延期的"ForEach"与立即执行的"ForEach"相比,有什么好处?延迟(这对于如何使用ForEach不方便)并没有改善这样一个事实,即如果Action有副作用,那么ForEach只执行有用的工作[通常对这样的Linq操作员提出的抱怨].那么,*这个变化有何帮助?另外,添加"ToList()"以强制它执行,没有用处.这是一场等待发生的事故."ForEach"的观点是它的副作用.如果你想要一个返回的枚举,SELECT会更有意义. (2认同)

cdi*_*ins 20

保持你的副作用超出我的IEnumerable

我想在LINQ中做相同的以下内容,但我无法弄清楚如何:

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

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

foreach (var i in items) i.DoStuff();
Run Code Online (Sandbox Code Playgroud)

我打赌你不想要副作用

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

一些例子

如果您实际上只是聚合(累积)某个值,那么您应该考虑Aggregate扩展方法.

items.Aggregate(initial, (acc, x) => ComputeAccumulatedValue(acc, x));
Run Code Online (Sandbox Code Playgroud)

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

items.Select(x => Transform(x));
Run Code Online (Sandbox Code Playgroud)

或者您可能想要创建一个查找表:

items.ToLookup(x, x => GetTheKey(x))
Run Code Online (Sandbox Code Playgroud)

可能性的列表(不完全打算)继续下去.

  • 啊,所以Select是Map,Aggregate是Reduce. (7认同)

reg*_*bsb 12

如果您想充当枚举卷,则应该生成每个项目.

public static class EnumerableExtensions
{
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
    {
        foreach (var item in enumeration)
        {
            action(item);
            yield return item;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


l33*_*33t 12

这么多答案,但都未能指出自定义通用 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

乍一看,所有三个变体的表现都应该一样好。然而,当ForEach扩展方法被调用很多,很多时候,你将结束垃圾暗示昂贵的GC。事实上,ForEach在热路径上使用这种扩展方法已被证明会完全降低循环密集型应用程序的性能。

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

强类型 foreach:内存使用

没有分配。 无GC

每周输入的 foreach:内存使用情况

在此处输入图片说明

ForEach 扩展:内存使用

大量分配。 重型GC。

分析

对于强类型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()
Run Code Online (Sandbox Code Playgroud)

对比

IL_0001:  callvirt   instance valuetype
          [mscorlib]System.Collections.Generic.List`1/Enumerator<!0>
          class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
Run Code Online (Sandbox Code Playgroud)

结论

该任择议定书问如何调用ForEach()IEnumerable<T>。原来答案清楚地表明它如何做到。当然你可以做到,但话又说回来了;我的回答清楚地表明你不应该。

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


Joh*_*ger 10

微软推出了针对LINQInteractive Extensions实验版(也在NuGet上,有关更多链接,请参阅RxTeams的配置文件).第9频道的视频解释得很好.

其文档仅以XML格式提供.我在Sandcastle中运行此文档,以使其具有更易读的格式.解压缩docs存档并查找index.html.

在许多其他好东西中,它提供了预期的ForEach实现.它允许您编写如下代码:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };

numbers.ForEach(x => Console.WriteLine(x*x));
Run Code Online (Sandbox Code Playgroud)

  • Interactive Extensions的工作链接:http://www.microsoft.com/download/en/details.aspx?id = 27203 (2认同)

Wol*_*lf5 8

根据PLINQ(自.Net 4.0以来可用),你可以做到

IEnumerable<T>.AsParallel().ForAll() 
Run Code Online (Sandbox Code Playgroud)

在IEnumerable上做一个并行的foreach循环.

  • 真正.它不是线程安全的,从某个线程池中将使用不同的线程......我需要修改我的答案.我现在尝试时没有ForEach().当我思考这个问题时,我的代码必须是包含ForEach扩展的代码. (2认同)

Tor*_*mod 6

ForEach的目的是引起副作用.IEnumerable用于集合的延迟枚举.

当您考虑它时,这种概念差异是非常明显的.

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

在您执行"计数"或"ToList()"或其他操作之前,这不会执行.显然不是表达的内容.

您应该使用IEnumerable扩展来设置迭代链,根据各自的源和条件定义内容.表达树是强大而有效的,但你应该学会欣赏它们的本性.而不仅仅是围绕它们进行编程以保存一些字符来覆盖惰性评估.


Nen*_*nad 5

很多人提到它,但是我不得不写下来。这不是最清晰/最易读的吗?

IEnumerable<Item> items = GetItems();
foreach (var item in items) item.DoStuff();
Run Code Online (Sandbox Code Playgroud)

简短(st)。

  • 现在,当我阅读我的评论时,我似乎似乎正在告诉您一些您不知道的事情。我不是那个意思 我的意思是`foreach(GetItems()中的var item)item.DoStuff();`只是一种美。 (4认同)
  • 当然。这样做是为了清楚起见。这样很清楚`GetItems()`方法返回什么。 (3认同)