我有一个Enumerable<T>
,我正在寻找一种方法,允许我为每个元素执行一个动作,类似于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; })
,但它没有打印任何内容。
答案 0 :(得分:51)
快速简便的方法是:
Names.ToList().ForEach(e => ...);
答案 1 :(得分:18)
您正在寻找目前仅存在于List通用集合中的难以捉摸的ForEach。网上有很多关于微软应该或不应该将其作为LINQ方法添加的讨论。目前,您必须自己动手:
public static void ForEach<T>(this IEnumerable<T> value, Action<T> action)
{
foreach (T item in value)
{
action(item);
}
}
虽然All()
方法提供了类似的功能,但它的用例是对每个项目而不是操作执行谓词测试。当然,可以说服它执行其他任务,但这会稍微改变语义并使其他人更难解释您的代码(即使用All()
进行谓词测试或操作?)。 / p>
答案 2 :(得分:8)
免责声明:此帖不再类似于我原来的答案,而是融入了我从那以后获得的七年经验。我进行了编辑,因为这是一个高度重视的问题,现有的答案都没有涵盖所有角度。如果您想查看我的原始答案,可以在revision history for this post中找到。
首先要理解的是C#linq操作,如Select()
,All()
,Where()
等,其根源在functional programming。我们的想法是为.Net世界带来一些更有用和更易于使用的函数式编程部分。这很重要,因为函数式编程的一个关键原则是这些操作应该没有副作用。很难低估这一点。添加each()
或ForEach()
操作不仅仅是在其他linq运算符的函数式编程范围之外,而是与它们直接对立。
但我明白这感觉不满意。你有一个真正需要解决的问题。为什么所有这些象牙塔哲学都会妨碍可能真正有用的东西呢?
当时参加C#设计团队的Eric Lippert可以帮助我们。 He recommends just using a traditional foreach
loop:
[ForEach()]为语言增加了零代表性能力。这样做可以让你重写这个非常清晰的代码:
foreach(Foo foo in foos){ statement involving foo; }
进入此代码:
foos.ForEach(foo=>{ statement involving foo; });
他的观点是,当您仔细查看语法选项时,从ForEach()
扩展名与传统foreach
循环相比,您无法获得任何新内容。我部分不同意。想象一下你有这个:
foreach(var item in Some.Long(and => possibly)
.Complicated(set => ofLINQ)
.Expression(to => evaluate)
{
// now do something
}
此代码模糊了含义,因为它将foreach
关键字与循环中的操作分开。它还列出了循环命令 previous 到定义循环操作顺序的操作。想要首先执行这些操作,然后在查询定义的 end 处使用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()
扩展方法的两个功能:
.Where()
循环与其余代码内联后,我无法再添加GroupBy()
或OrderBy()
或foreach
,而无需创建新语句。foreach
并不那么简单。您当然可以制作自己的ForEach()
扩展方法。其他几个答案已经实现了这种方法;这并不是那么复杂。但是,我觉得这是不必要的。现有的方法已经适合我们想要从语义和操作角度做的事情。上述两个缺失的功能都可以通过使用现有的Select()
运算符来解决。
Select()
符合上述两个示例所描述的转换或投影。但请记住,我仍然会避免产生副作用。对Select()
的调用应该返回来自原件的新对象或投影。这有时可以通过使用匿名类型或动态对象来帮助(当且仅在必要时)。如果您需要将结果保留在原始列表变量中,则可以随时调用.ToList()
并将其分配回原始变量。我将在此处添加,我希望尽可能多地使用IEnumerable<T>
变量来处理更具体的类型。
myList = myList.Select(item => new SomeType(item.value1, item.value2 *4)).ToList();
总结:
foreach
。foreach
真的不行时(可能不像您想象的那么频繁),请使用Select()
Select()
时,您仍然可以避免(程序可见的)副作用,可能是通过投射到匿名类型。答案 3 :(得分:6)
不幸的是,在当前版本的LINQ中没有内置的方法可以做到这一点。框架团队忽略了添加.ForEach扩展方法。现在在下面的博客上就此进行了很好的讨论。
http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx
虽然添加一个很容易。
public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action) {
foreach ( var cur in enumerable ) {
action(cur);
}
}
答案 4 :(得分:4)
你无法立即使用LINQ和IEnumerable - 你需要实现自己的扩展方法,或者使用LINQ将枚举转换为数组,然后调用Array.ForEach():
Array.ForEach(MyCollection.ToArray(), x => x.YourMethod());
请注意,由于值类型和结构的工作方式,如果集合是值类型,并且您以这种方式修改集合的元素,它将不会影响原始集合的元素。
答案 5 :(得分:2)
因为LINQ被设计为查询功能而不是更新功能,所以您将找不到在IEnumerable&lt; T&gt;上执行方法的扩展。因为这将允许您执行一个方法(可能有副作用)。在这种情况下,您可以坚持使用
foreach(名称中的字符串名称)
Console.WriteLine(名称);
答案 6 :(得分:1)
好吧,您也可以使用标准foreach
关键字,只需将其格式化为oneliner:
foreach(var n in Names.Where(blahblah)) DoStuff(n);
抱歉,我认为这个选项应该在这里:)
答案 7 :(得分:0)
列表中有一个ForEach方法。您可以通过调用.ToList()方法将Enumerable转换为List,然后调用ForEach方法。
或者,我听说有人从IEnumerable定义他们自己的ForEach方法。这可以通过实质上调用ForEach方法来实现,而是将其包装在扩展方法中:
public static class IEnumerableExtensions
{
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> _this, Action<T> del)
{
List<T> list = _this.ToList();
list.ForEach(del);
return list;
}
}
答案 8 :(得分:0)
使用Parallel Linq:
Names.AsParallel()。ForAll(name =&gt; ...)
答案 9 :(得分:-1)
如前所述,ForEach扩展程序将解决此问题。
我对当前问题的提示是如何执行迭代器
[我确实尝试过Select(s => {Console.WriteLine(s); return s;}),但它没有打印任何内容。]
检查
_ = Names.Select(s => {Console.WriteLine(s); return 0;})。Count();
尝试一下!