对Enumerable <t> </t>中的所有元素执行特定操作

时间:2009-02-09 18:02:38

标签: linq c#-3.0 enumerable

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

10 个答案:

答案 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()扩展方法的两个功能:

  1. 它不可组合。在.Where()循环与其余代码内联后,我无法再添加GroupBy()OrderBy()foreach,而无需创建新语句。
  2. 这不是懒惰。这些操作立即发生。例如,它不允许我有一个表单,其中用户选择操作作为较大屏幕中的一个字段,直到用户按下命令按钮才对其起作用。此表单可能允许用户在执行命令之前改变主意。使用LINQ查询这是完全正常的( easy even),但foreach并不那么简单。
  3. 您当然可以制作自己的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()时,您仍然可以避免(程序可见的)副作用,可能是通过投射到匿名类型。

答案 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();

尝试一下!