LINQ相当于IEnumerable <T>的foreach


717

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

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

真正的语法是什么?


99
考虑一下Eric Lippert的“ foreach”与“ ForEach”。它为使用/ 提供提供了很好的理由ForEach()

4
@pst:我曾经盲目地将这种扩展方法粘贴在我的项目中。感谢您的文章。
罗宾·马本


2
尽管不是很性感,但它适合于一行而无需通过ToList创建副本foreach (var i in items) i.Dostuff();
James Westgate

1
这些链接中的某些链接已消失!什么是这么多投票的文章!
沃伦

Answers:


863

没有ForEach扩展IEnumerable;只为List<T>。所以你可以做

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

或者,编写您自己的ForEach扩展方法:

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

213
注意ToList(),因为ToList()创建序列的副本,这可能会导致性能和内存问题。
decasteljau,2009年

15
有几个理由使“ .Foreach()”变得更好。1. PLINQ可能实现多线程。2.异常,您可能想在循环中收集所有异常并立即将其抛出;它们是噪音代码。
丹尼斯C,2010年

12
PLINQ使用ForAll而不使用ForEach,这就是为什么您不使用ForEach的原因。
user7116

9
您应该IENumerable<T>在扩展方法中返回。喜欢:public static IEnumerable<T> ForAll<T>(this IEnumerable<T> numeration, Action<T> action) { foreach (T item in enumeration) { action(item); } return enumeration; }
Kees C. Bakker

22
请注意,在第一种情况下,开发人员1)几乎不保存任何键入内容,而2)不必要地分配了内存以将整个序列存储为列表,然后再将其丢弃。想想你之前LINQ
马克Sowul

364

Fredrik提供了此修复程序,但值得一提的是为什么它不在框架之内。我相信LINQ查询运算符应该是无副作用的,并且适合以合理的方式查看世界。显然,ForEach正好相反- 纯粹是基于副作用的构造。

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


44
然而,F#拥有Seq.iter
Stephen Swensen 2010年

6
但是,有一个非常有用的事情,LINQ函数通常提供了foreach()不提供的条件-Linq函数采用的匿名函数也可以将索引作为参数,而使用foreach则必须为(int i。 )构造。功能语言必须允许迭代产生副作用-否则,您将永远无法使用它们进行工作:)我想指出的是,典型的功能方法将是返回原始的可枚举值不变。
Walt W 2010年

3
@Walt:我仍然不确定我是否同意,部分原因是典型的真正功能性方法不会像这样使用 foreach……它将使用更类似于LINQ的功能性方法来创建新序列。
乔恩·斯基特

12
@Stephen Swensen:是的,F#具有Seq.iter,但F#也具有不变的数据结构。在这些上使用Seq.iter时,原始Seq不会被修改;返回带有侧面影响元素的新Seq。
安德斯,

7
@Anders- Seq.iter不返回任何内容,更不用说带有侧面影响元素的新序列了。这实际上只是副作用。您可能会想到Seq.map,因为Seq.iter它完全等效于上的ForEach扩展方法IEnumerabe<_>
乔尔·穆勒

39

2012年7月17日更新:显然,自C#5.0起,以下所述的行为foreach已更改,并且“ 在嵌套的lambda表达式中使用foreach迭代变量不再产生意外结果。 ”此答案不适用于C#≥5.0 。

@John Skeet和每个喜欢foreach关键字的人。

5.0之前的 C#中的“ foreach”存在的问题是,它与等效的“ for comprehension”在其他语言中的工作方式以及我希望它的工作方式不一致(个人意见仅在此陈述,因为其他人提到了他们的关于可读性的意见)。请参阅有关“ 访问修改后的闭包 ”以及“ 关闭认为有害的循环变量 ”的所有问题。这只是“有害”,因为在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,因为要做这样的事情并不遥不可及。显然,有更好的方法来创建此可观察的对象,我只是在尝试说明一个观点。通常,预订可观察对象的代码是异步执行的,并且可能在另一个线程中执行。如果使用“ 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 Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Win
    Assert.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>

[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 warning
                                    source.OnNext(() => value);
                                }
                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Fail
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}

转售者警告说什么?
reggaeguitar

1
@reggaeguitar自从发布C#5.0以来,已有7或8年没有使用C#了,这改变了此处描述的行为。当时它发出了这个警告stackoverflow.com/questions/1688465/…。我怀疑它仍然会对此发出警告。
drstevens

34

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

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

11
我也同意 这可能是一个聪明的小技巧,但是乍一看,与标准的foreach循环相比,此代码尚不清楚其功能。
康奈尔

26
我不得不投反对票,因为尽管从技术上讲是正确的,但是您未来的自我和您的所有继任者将永远恨您,因为而不是foreach(Item i in GetItems()) { i.DoStuff();}您带了更多的角色并使之极为混乱
Mark Sowul 2013年

14
好的,但是更items.All(i => { i.DoStuff(); return true; }
容易理解的是

5
我知道这是一个相当老的帖子,但是我也不得不对其投下反对票。我同意这是一个聪明的把戏,但这是无法维护的。乍一看,似乎只对列表中的第一项进行了处理。将来,由于编码人员误解了它的功能以及应该做什么,这很容易导致更多错误。

4
这是副作用编程,不鼓励恕我直言。
阿尼什(Anish)2015年

21

我采用了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包含空字符串,您将看到测试失败。这是因为没有方法强制ForEach枚举。


您的替代实现ForEach很有趣,但是与List.ForEach的签名不匹配的行为不匹配public void ForEach(Action<T> action)。它也不符的行为Observable.ForEach扩展IObservable<T>,签名,其中是public static void ForEach<TSource>(this IObservable<TSource> source, Action<TSource> onNext)。FWIW,与ForEachScala集合等效,即使是那些懒惰的集合,也具有返回类型,该返回类型等效于C#中的void。
drstevens

这是最好的答案:延迟执行+流利的api。
亚历山大·丹尼洛夫

3
这工作完全一样调用SelectToList。的目的ForEach是不必致电ToList。它应该立即执行。
t3chb0t 2016年

3
与立即执行的“ ForEach”相比,推迟执行的“ ForEach”有什么好处?延期(这对使用ForEach的方式带来不便)并不能改善以下事实:ForEach仅在Action产生副作用时才执行有用的工作(针对这种Linq运算符的常见投诉)。那么,如何这种改变帮助?另外,添加的“ ToList()”强制其执行,没有任何用处。等待发生的事故。“ ForEach”的要点是它的副作用。如果您想要返回的枚举,则SELECT更有意义。
ToolmakerSteve

20

让您的副作用远离我的IEnumerable

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

正如其他人国内外所指出的那样,LINQ和IEnumerable方法有望实现无副作用。

您是否真的要对IEnumerable中的每个项目执行“操作”?然后foreach是最佳选择。当这里发生副作用时,人们并不感到惊讶。

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

我敢打赌你不要副作用

但是根据我的经验,通常不需要副作用。Jon Skeet,Eric Lippert或Marc Gravell常常会解释一个简单的LINQ查询,并等待StackOverflow.com的回答,解释如何做自己想做的!

一些例子

如果您实际上只是在汇总(累加)某个值,则应考虑Aggregate扩展方法。

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

也许您想IEnumerable根据现有值创建一个新值。

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

或者,也许您想创建一个查找表:

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

可能性的清单(并非完全是双关语)不断出现。


7
嗯,所以选择是Map,聚合是Reduce。
Darrel Lee

17

如果要充当枚举卷,则应产生每个项目。

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

当您退回商品时,这并不是真正的ForEach,更像是Tap。
EluciusFTW

10

Microsoft提供了一个LINQ交互式扩展的实验性发布(也位于NuGet上,有关更多链接,请参见RxTeams的个人资料)。第9频道的视频对此进行了很好的解释。

其文档仅以XML格式提供。我已经在Sandcastle中运行了此文档,以使其更具可读性。解压缩文档档案并查找index.html

在许多其他功能中,它提供了预期的ForEach实施。它允许您编写如下代码:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };

numbers.ForEach(x => Console.WriteLine(x*x));

2
交互式扩展的工作链接:microsoft.com/download/en/details.aspx?
id=27203

8

根据PLINQ(自.Net 4.0起可用),您可以执行

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

在IEnumerable上执行并行的foreach循环。


只要您的操作是线程安全的,一切都很好。但是,如果您要执行非线程安全的操作,将会发生什么?它会引起很大的混乱……
shirbr510 '16

2
真正。它不是线程安全的,将在某个线程池中使用不同的线程...而且我需要修改答案。当我现在尝试时,没有ForEach()。当我思考此问题时,一定是我的代码包含带有ForEach的扩展。
Wolf5

6

ForEach的目的是引起副作用。IEnumerable用于集合的惰性枚举。

考虑到这一概念上的差异是显而易见的。

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

直到您执行“计数”或“ ToList()”或其上的任何操作后,此命令才会执行。显然不是所表达的。

您应该使用IEnumerable扩展来设置迭代链,并根据其各自的来源和条件来定义内容。表达式树功能强大且高效,但是您应该学会欣赏它们的本质。而且不只是为了围绕它们进行编程以保存一些覆盖懒惰求值的字符。


5

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

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

简短而简单。


您可以删除整个第一行并直接在foreach中使用GetItems
tymtam '16

3
当然。这样做是为了清楚起见。这样很清楚什么GetItems()方法返回。
内纳德

4
现在,当我阅读我的评论时,我似乎似乎正在告诉您一些您不知道的事情。我不是那个意思 我的意思是那foreach (var item in GetItems()) item.DoStuff();只是一种美。
tymtam '16

4

现在我们可以选择...

        ParallelOptions parallelOptions = new ParallelOptions();
        parallelOptions.MaxDegreeOfParallelism = 4;
#if DEBUG
        parallelOptions.MaxDegreeOfParallelism = 1;
#endif
        Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));

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

ps(对不起字体,这是系统决定的)


4

正如许多答案所指出的那样,您可以轻松地自己添加这样的扩展方法。但是,如果您不想这样做,尽管我不了解BCL中的类似信息,但System如果您已经引用了Reactive Extension(并且如果您没有, 你应该有):

using System.Reactive.Linq;

items.ToObservable().Subscribe(i => i.DoStuff());

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


似乎该包装已被弃用
reggaeguitar

3

ForEach也可以被Chained,只需在动作后放回桩线即可。保持流利


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


//conditional
Employees
    .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;
    }
}

懒惰的版本需要被物化,ToList()例如,否则,什么都不会发生。请参见下面来自ToolmakerSteve的精彩评论。

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

我将ForEach()和ForEachLazy()都保留在我的库中。


2
你测试过了吗?在多种情况下,此方法的行为会直观地相反。更好的是foreach(T item in enu) {action(item); yield return item;}
Taemyr

2
这将导致多个枚举
regisbsb

有趣。我想在上面进行上述操作时,考虑3个动作的序列,而不是foreach (T item in enu) { action1(item); action2(item); action3(item); }?单个显式循环。我想在开始执行action2之前执行所有 action1 是否重要。
ToolmakerSteve

@ Rm558-您使用“收益率回报”的方法。Pro:仅枚举一次原始序列,因此可以在更大范围的枚举中正常工作。缺点:如果最后忘记了“ .ToArray()”,则什么也不会发生。尽管失败将是显而易见的,但长期不会被忽视,因此代价不菲。对我来说不是“感觉干净”,但如果有人走这条路(ForEach运营商),这是正确的权衡。
制造商

1
从事务的角度来看,此代码并不安全。如果失败了ProcessShipping()怎么办?您向买家发送电子邮件,从他的信用卡中扣款,但从不向他发送东西吗?当然要问问题。
zmechanic

2

这种“功能方法”的抽象会浪费大量时间。语言层面上没有任何东西可以防止副作用。只要您可以使其对容器中的每个元素都调用lambda / delegate-您将获得“ ForEach”行为。

例如,这里将srcDictionary合并到destDictionary中的一种方法(如果密钥已经存在-覆盖)

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

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

6
如果您必须添加该免责声明,则可能不应该发布它。只是我的观点。
安德鲁·格罗斯

2
比这更好foreach(var tuple in srcDictionary) { destDictionary[tuple.Key] = tuple.Value; }吗?如果不在生产代码中,那么为什么在何处使用此代码?
Mark Sowul

3
这仅是为了显示原则上是可能的。它也恰好可以满足OP的要求,我清楚地表明它不应该在生产中使用,它仍然很有趣并且具有教育意义。
Zar Shardan

2

受乔恩·斯基特(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);
    }


1

我尊重不同的观点,即链接扩展方法应无副作用(不仅因为它们不是,任何委托都可以执行副作用)。

考虑以下:

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

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


9
扩展方法是否可能产生副作用与是否应该产生副作用之间存在差异。您仅指出它可以具有,尽管有很好的理由建议它们不具有副作用。在函数式编程中,所有函数都是无副作用的,使用函数式编程结构时,您可能希望假设它们是无副作用的。
Dave Van den Eynde

1

要保持流利,可以使用以下技巧:

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


-1

另一个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;
}

4
真是浪费内存。这将调用ToList添加到列表。为什么这不只是返回address.Select(a => MapToDomain(a))?
Mark Sowul
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.