LINQ等价于IENumable<T>的foreach

我想在LINQ中执行以下等效操作,但我不知道如何:

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

真正的语法是什么?

653517 次浏览

没有IEnumerable的For每个扩展;只有List<T>。所以你可以这样做

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

或者,编写您自己的For每个扩展方法:

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

Fredrik已经提供了解决方案,但也许值得考虑一下为什么这不在框架中。我相信这个想法是LINQ查询运算符应该没有副作用,适合一种功能合理的看待世界的方式。显然For每是完全相反的——一个基于纯粹副作用的构造。

这并不是说这是一件坏事——只是想想这个决定背后的哲学原因。

我不同意链接扩展方法应该是无副作用的概念(不仅因为它们不是,任何委托都可以执行副作用)。

考虑以下几点:

   public class Element {}
public Enum ProcessType{This = 0, That = 1, SomethingElse = 2}
public class Class1{private Dictionary<ProcessType, Action<Element>> actions =new Dictionary<ProcessType,Action<Element>>();
public Class1(){actions.Add( ProcessType.This, DoThis );actions.Add( ProcessType.That, DoThat );actions.Add( ProcessType.SomethingElse, DoSomethingElse );}
// Element actions:
// This example defines 3 distict actions// that can be applied to individual elements,// But for the sake of the argument, make// no assumption about how many distict// actions there may, and that there could// possibly be many more.
public void DoThis( Element element ){// Do something to element}
public void DoThat( Element element ){// Do something to element}
public void DoSomethingElse( Element element ){// Do something to element}
public void Apply( ProcessType processType, IEnumerable<Element> elements ){Action<Element> action = null;if( ! actions.TryGetValue( processType, out action ) )throw new ArgumentException("processType");foreach( element in elements )action(element);}}

这个例子所展示的实际上只是一种后期绑定,它允许调用对元素序列有副作用的许多可能的动作之一,而不必编写一个大的开关构造来解码定义动作的值并将其转换为相应的方法。

我采用了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;}}

以下是帮助查看它的测试:

[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"));}

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

ForEach示例

public static IList<AddressEntry> MapToDomain(IList<AddressModel> addresses){var workingAddresses = new List<AddressEntry>();
addresses.Select(a => a).ToList().ForEach(a => workingAddresses.Add(AddressModelMapper.MapToDomain(a)));
return workingAddresses;}

的目的是造成副作用。IENumable用于延迟枚举集合。

当你考虑它时,这种概念上的差异是非常明显的。

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

这不会执行,直到你做一个“计数”或“ToList()”或它的东西。这显然不是所表达的。

你应该使用IENumable扩展来建立迭代链,根据它们各自的来源和条件定义内容。表达式树功能强大且高效,但你应该学会欣赏它们的本质。而不仅仅是为了围绕它们进行编程,以节省几个字符,而不是延迟评估。

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

IEnumerable<Item> items = GetItems();items.FirstOrDefault(i => { i.DoStuff(); return false; });

现在我们可以选择…

        ParallelOptions parallelOptions = new ParallelOptions();parallelOptions.MaxDegreeOfParallelism = 4;#if DEBUGparallelOptions.MaxDegreeOfParallelism = 1;#endifParallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));

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

ps(字体不好意思,系统决定的)

微软有一个实验版本LINQ的交互式扩展(也是在NuGet,更多链接见RxTeams的个人资料)。第9频道视频很好地解释了它。

它的文档仅以XML格式提供。我运行了这个留档沙堡以使其具有更可读的格式。解压缩文档存档并查找index.html

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

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };
numbers.ForEach(x => Console.WriteLine(x*x));

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

@John Skeet和所有喜欢Foreach关键字的人。

在C#5.0之前的版本中使用"foreach"的问题是,它与其他语言中等效的"用于理解"的工作方式以及我期望它的工作方式不一致(这里陈述个人意见只是因为其他人提到了他们对易读性的看法)。查看有关"访问修改闭包"的所有问题以及“关闭循环变量被认为是有害的”。这仅仅是“有害的”,因为在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);}}}

为这个过于做作的例子道歉。我使用Observable只是因为做这样的事情并不完全牵强。显然有更好的方法来创建这个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 Funcsvar evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();
//WinAssert.That(evaluatedObservable,Is.EquivalentTo(values.ToList()));}

以下操作失败并出现错误:

预期:相当于<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 warningsource.OnNext(() => value);}source.OnCompleted();return () => { };});
//Simulate subscribing and evaluating Funcsvar evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();
//FailAssert.That(evaluatedObservable,Is.EquivalentTo(values.ToList()));}

这种“函数式方法”抽象泄漏了很大的时间。语言级别没有任何东西可以防止副作用。只要你可以让它为容器中的每个元素调用你的lambda/委托-你将获得“For每个”行为。

这里举个例子,一种将srcDicpedia合并到运算符中的方法(如果key已经存在-覆盖)

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

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

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

IEnumerable<Item> items = GetItems();foreach (var item in items) item.DoStuff();

短而简单(st)。

把你的副作用排除在我的IENumable之外

我想在LINQ中执行以下等效操作,但我不知道如何:

正如其他人所指出的这里和国外LINQ和IEnumerable方法预计是无副作用的。

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

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

我打赌你不想有副作用

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

一些例子

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

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

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

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

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

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

可能性的清单(双关语不完全是故意的)还在继续。

VB.NET你应该使用:

listVariable.ForEach(Sub(i) i.Property = "Value")

正如许多答案已经指出的那样,你可以自己轻松添加这样的扩展方法。然而,如果你不想这样做,尽管我不知道在BCL中有这样的事情,如果你已经有对反应式扩展的引用(如果你没有,你应该有),System命名空间中仍然有一个选项:

using System.Reactive.Linq;
items.ToObservable().Subscribe(i => i.DoStuff());

尽管方法名称有点不同,但最终结果正是您要寻找的。

受Jon Skeet的启发,我扩展了他的解决方案:

扩展方法:

public static void Execute<TSource, TKey>(this IEnumerable<TSource> source, Action<TKey> applyBehavior, Func<TSource, TKey> keySelector){foreach (var item in source){var target = keySelector(item);applyBehavior(target);}}

客户:

var jobs = new List<Job>(){new Job { Id = "XAML Developer" },new Job { Id = "Assassin" },new Job { Id = "Narco Trafficker" }};
jobs.Execute(ApplyFilter, j => j.Id);

。..

    public void ApplyFilter(string filterId){Debug.WriteLine(filterId);}

也可以是链式,只是在动作之后放回柱头线。


Employees.ForEach(e=>e.Act_A).ForEach(e=>e.Act_B).ForEach(e=>e.Act_C);
Orders  //just for demo.ForEach(o=> o.EmailBuyer() ).ForEach(o=> o.ProcessBilling() ).ForEach(o=> o.ProcessShipping());

//conditionalEmployees.ForEach(e=> {  if(e.Salary<1000) e.Raise(0.10);}).ForEach(e=> {  if(e.Age   >70  ) e.Retire();});

渴望版本的实现。

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enu, Action<T> action){foreach (T item in enu) action(item);return enu; // make action Chainable/Fluent}

编辑:懒惰版本使用的是收益率返回,就像这个一样。

public static IEnumerable<T> ForEachLazy<T>(this IEnumerable<T> enu, Action<T> action){foreach (var item in enu){action(item);yield return item;}}

Lazy版本需要要物化,例如ToList(),否则,什么都不会发生。

IQueryable<Product> query = Products.Where(...);query.ForEachLazy(t => t.Price = t.Price + 1.00).ToList(); //without this line, below SubmitChanges() does nothing.SubmitChanges();

我在我的库中同时保存了For每个()和ForEachLazy()。

如果您想充当枚举卷,您应该为每个项目产量

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;}}}

根据PLINQ(可从. Net 4.0获得),您可以执行

IEnumerable<T>.AsParallel().ForAll()

在IENumable上做一个并行的Foreach循环。

MoreLinq有IEnumerable<T>.ForEach和大量其他有用的扩展。仅为ForEach使用依赖项可能不值得,但那里有很多有用的东西。

https://www.nuget.org/packages/morelinq/

https://github.com/morelinq/MoreLINQ

要保持流利,可以使用这样的技巧:

GetItems().Select(i => new Action(i.DoStuf))).Aggregate((a, b) => a + b).Invoke();

这么多的答案,但都没有指出自定义仿制药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:内存使用

不分配。没有GC

弱类型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()

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编译)。