我有一个Enumerable
并且正在寻找一种方法,允许我为每个元素执行一个动作,有点像Select
然后是副作用.就像是:
string[] Names = ...; Names.each(s => Console.Writeline(s));
要么
Names.each(s => GenHTMLOutput(s)); // (where GenHTMLOutput cannot for some reason receive the enumerable itself as a parameter)
我试过了Select(s=> { Console.WriteLine(s); return s; })
,但它没有打印任何东西.
一个快速简便的方法是:
Names.ToList().ForEach(e => ...);
您正在寻找目前仅存在于List通用集合中的难以捉摸的ForEach.网上有很多关于微软应该或不应该将其作为LINQ方法添加的讨论.目前,你必须自己动手:
public static void ForEach(this IEnumerable value, Action action) { foreach (T item in value) { action(item); } }
虽然该All()
方法提供了类似的能力,但它的用例是对每个项目而不是动作执行谓词测试.当然,可以说服它执行其他任务,但这会稍微改变语义并使其他人更难以解释您的代码(即这是All()
用于谓词测试还是动作?).
免责声明:这篇文章不再类似于我原来的答案,而是融入了我从那以后获得的七年经验.我进行了编辑,因为这是一个高度重视的问题,现有的答案都没有涵盖所有角度.如果您想查看我的原始答案,可以在此帖子的修订历史记录中找到.
在这里了解的第一件事是像C#LINQ操作Select()
,All()
,Where()
,等,其根源都在于函数式编程.我们的想法是为.Net世界带来一些更有用和更易于使用的函数式编程部分.这很重要,因为函数式编程的一个关键原则是这些操作应该没有副作用.很难低估这一点.添加ForEach()
或each()
操作不仅仅是在其他linq运算符的函数式编程范围之外,而是与它们直接对立.
但我明白这感到不满意.你有一个真正需要解决的问题.为什么所有这些象牙塔哲学都会妨碍可能真正有用的东西呢?
当时在C#设计团队工作的Eric Lippert可以帮助我们.他建议只使用传统的each()
循环:
[ForEach()]为语言增加了零新的表征能力.这样做可以让你重写这个完全清晰的代码:
ForEach()
进入这段代码:
foreach
他的观点是,当你仔细研究你的语法选项时,你不会从foreach(Foo foo in foos){ statement involving foo; }
扩展到传统foos.ForEach(foo=>{ statement involving foo; });
循环获得任何新东西.我部分不同意.想象一下你有这个:
foreach(var item in Some.Long(and => possibly) .Complicated(set => ofLINQ) .Expression(to => evaluate) { // now do something }
此代码模糊了含义,因为它将ForEach()
关键字与循环中的操作分开.它还列出了定义循环操作序列的操作之前的循环命令.想要将这些操作放在第一位然后在查询定义的末尾使用loop命令会感觉更自然.此外,代码只是丑陋.看起来能写这个会好得多:
Some.Long(and => possibly) .Complicated(set => ofLINQ) .Expression(to => evaluate) .ForEach(item => { // now do something });
然而,即使在这里,我最终还是以埃里克的观点出现了.我意识到你上面看到的代码正在呼唤一个额外的变量.如果你有一组复杂的LINQ表达式,你可以先将LINQ表达式的结果赋给一个新变量,然后在代码中添加一些有价值的信息:
var queryForSomeThing = Some.Long(and => possibly) .Complicated(set => ofLINQ) .Expressions(to => evaluate); foreach(var item in queryForSomeThing) { // now do something }
这段代码感觉更自然.它将foreach
关键字放回到循环的其余部分旁边,并在查询定义之后.最重要的是,变量名称可以添加新信息,这将有助于未来的程序员试图理解LINQ查询的目的.同样,我们看到所需的foreach
操作员确实没有为该语言添加新的表达能力.
但是,我们仍然缺少假设foreach
扩展方法的两个特征:
它不可组合.我不能添加另外ForEach()
或ForEach()
或者.Where()
一后GroupBy()
与其余代码回路内联,而无需创建一个新的语句.
这不是懒惰.这些操作立即发生.例如,它不允许我有一个表单,其中用户选择操作作为较大屏幕中的一个字段,直到用户按下命令按钮才对其起作用.此表单可能允许用户在执行命令之前改变主意.使用LINQ查询这是完全正常的(简单的),但不是那么简单OrderBy()
.
当然,您可以制作自己的foreach
扩展方法.其他几个答案已经实现了这种方法; 这并不是那么复杂.但是,我觉得这是不必要的.现有的方法已经适合我们想要从语义和操作角度做的事情.上述两个缺失的功能都可以通过使用现有的foreach
运营商来解决.
ForEach()
适合上述两个例子描述的变换或投影.但请记住,我仍然会避免产生副作用.调用Select()
应返回原始对象或投影.这有时可以通过使用匿名类型或动态对象来帮助(当且仅在必要时).如果您需要将结果保留在原始列表变量中,您可以随时调用Select()
并将其分配回原始变量.我在这里补充说,我喜欢Select()
在更具体的类型上尽可能地使用变量.
myList = myList.Select(item => new SomeType(item.value1, item.value2 *4)).ToList();
综上所述:
只是坚持.ToList()
大部分时间.
如果IEnumerable
真的不这样做(可能不像你想象的那么频繁),请使用foreach
当您需要使用时foreach
,您通常可以通过投射到匿名类型来避免(程序可见的)副作用.
不幸的是,在当前版本的LINQ中没有内置的方法可以做到这一点.框架团队忽略了添加.ForEach扩展方法.现在在下面的博客上就此进行了很好的讨论.
http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx
虽然添加一个很容易.
public static void ForEach(this IEnumerable enumerable, Action action) { foreach ( var cur in enumerable ) { action(cur); } }
您不能立即使用LINQ和IEnumerable进行此操作-您需要实现自己的扩展方法,或者使用LINQ将枚举强制转换为数组,然后调用Array.ForEach():
Array.ForEach(MyCollection.ToArray(), x => x.YourMethod());
请注意,由于值类型和结构的工作方式,如果集合属于值类型,并且您以这种方式修改集合的元素,则它将对原始集合的元素没有影响。