56

我有一个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; })过,但它没有打印任何东西。

4

10 回答 10

71

一种快速简便的方法是:

Names.ToList().ForEach(e => ...);
于 2009-02-09T18:17:03.883 回答
21

您正在寻找目前仅存在于 List 通用集合中的难以捉摸的 ForEach。网上有很多关于微软是否应该将其添加为 LINQ 方法的讨论。目前,您必须自己动手:

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

虽然该All()方法提供了类似的功能,但它的用例是对每个项目而不是操作执行谓词测试。当然,可以说服它执行其他任务,但这会在一定程度上改变语义,并使其他人更难解释您的代码(即,这是All()用于谓词测试还是动作?)。

于 2009-02-09T18:07:33.127 回答
20

免责声明:这篇文章不再类似于我原来的答案,而是结合了我从那以后获得的大约七年的经验。我进行了编辑,因为这是一个备受关注的问题,并且现有的答案都没有真正涵盖所有角度。如果您想查看我的原始答案,可以在此帖子的修订历史中找到


首先要了解的是 C# linq 操作,如Select()All()Where()等,其根源在于函数式编程。这个想法是将函数式编程的一些更有用和更容易理解的部分带到 .Net 世界。这很重要,因为函数式编程的一个关键原则是操作没有副作用。很难低估这一点。但是,在ForEach()/的情况下each(),副作用是操作的全部目的。添加each()orForEach()不仅超出了其他 linq 运算符的函数式编程范围,而且与它们直接相反。

但我知道这感觉不令人满意。它可能有助于解释为什么ForEach()从框架中省略,但无法解决手头的真正问题。你有一个真正的问题需要解决。为什么所有这些象牙塔哲学都会妨碍一些可能真正有用的东西?

当时在 C# 设计团队工作的 Eric Lippert 可以在这里为我们提供帮助。他建议使用传统的foreach循环:

[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关键字与循环中的操作分开。它还在定义循环操作顺序的操作之前列出循环命令。让这些操作先出现感觉更自然,然后在查询定义的末尾使用循环命令。此外,代码很丑陋。似乎能够这样写会更好:

Some.Long(and => possibly)
   .Complicated(set => ofLINQ)
   .Expression(to => evaluate)
   .ForEach(item => 
{
    // now do something
});

然而,即使在这里,我最终还是接受了 Eric 的观点。我意识到你在上面看到的代码正在调用一个额外的变量。如果您有一组复杂的 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()扩展方法的两个特征:

  1. 它不是可组合的。如果不创建新语句,我无法在与其余代码内联的循环之后添加进一步的.Where()GroupBy()或。OrderBy()foreach
  2. 这不是懒惰。这些操作立即发生。例如,它不允许我有一个表单,其中用户选择一个操作作为大屏幕中的一个字段,直到用户按下命令按钮才起作用。这种形式可能允许用户在执行命令之前改变主意。对于 LINQ 查询,这是完全正常的(甚至很容易foreach),但对于.

(FWIW,大多数幼稚.ForEach()的实现也有这些问题。但是没有它们也可以制作一个。)

当然,您可以制作自己的ForEach()扩展方法。其他几个答案已经实现了这种方法;这并不是那么复杂。不过,我觉得没必要。从语义和操作的角度来看,已经有一种适合我们想要做的现有方法。上述两个缺失的特性都可以通过使用现有Select()操作来解决。

Select()符合上述两个示例所描述的变换或投影类型。但请记住,我仍然会避免产生副作用。调用Select()应该从原始对象返回新对象或投影。这有时可以通过使用匿名类型或动态对象来帮助(如果且​​仅在必要时)。如果您需要将结果保存在原始列表变量中,您可以随时调用.ToList()并将其分配回原始变量。我将在这里补充一点,我更喜欢IEnumerable<T>尽可能多地使用变量而不是更具体的类型。

myList = myList.Select(item => new SomeType(item.value1, item.value2 *4)).ToList();

总之:

  1. foreach大部分时间都坚持。
  2. 当真的不行时(这可能不像你想象的那么频繁),foreach 使用Select()
  3. 当您需要使用Select()时,您通常仍然可以避免(程序可见的)副作用,可能通过投影到匿名类型。
  4. 避免打电话的拐杖ToList()。您并不像您想象的那样需要它,它可能会对性能和内存使用产生重大负面影响。
于 2009-02-09T18:07:22.347 回答
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);
  }
}
于 2009-02-09T18:08:43.263 回答
5

您不能立即使用 LINQ 和 IEnumerable 执行此操作 - 您需要实现自己的扩展方法,或者使用 LINQ 将枚举转换为数组,然后调用 Array.ForEach():

Array.ForEach(MyCollection.ToArray(), x => x.YourMethod());

请注意,由于值类型和结构的工作方式,如果集合是值类型并且您以这种方式修改集合的元素,它将不会影响原始集合的元素。

于 2009-02-09T18:08:05.717 回答
2

因为 LINQ 被设计为查询功能而不是更新功能,所以您不会找到在 IEnumerable<T> 上执行方法的扩展,因为这将允许您执行方法(可能会产生副作用)。在这种情况下,你不妨坚持

foreach(Names 中的字符串名称)
Console.WriteLine(name);

于 2009-02-09T18:13:40.023 回答
2

使用并行 Linq:

Names.AsParallel().ForAll(name => ...)

于 2015-10-29T08:23:40.717 回答
1

好吧,您也可以使用标准foreach关键字,只需将其格式化为 oneliner:

foreach(var n in Names.Where(blahblah)) DoStuff(n);

抱歉,我认为这个选项值得在这里 :)

于 2015-05-21T18:47:35.890 回答
0

List 中有一个 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;
    }
}
于 2009-02-09T18:07:55.980 回答
-1

如前所述,ForEach 扩展将进行修复。

对当前问题的提示是如何执行迭代器

[我确实尝试过 Select(s=> { Console.WriteLine(s); return s; }),但它没有打印任何东西。]

检查这个

_= Names.Select(s=> { Console.WriteLine(s); return 0; }).Count();

Try it!

于 2020-10-05T21:21:43.880 回答