LINQ相当于foren for IEnumerable

LINQ equivalent of foreach for IEnumerable<T>

我想在Linq中执行以下等效操作,但我不知道如何执行:

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

真正的语法是什么?


IEnumerable没有foreach扩展;只有List有foreach扩展。所以你可以这样做

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

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

1
2
3
4
5
6
7
public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach(T item in enumeration)
    {
        action(item);
    }
}


Fredrik已经提供了解决方案,但可能值得考虑的是,为什么这不在框架中。我相信这个想法是,LINQ查询操作符应该是无副作用的,符合一种合理的功能性看待世界的方式。显然foreach恰恰相反——一个纯粹基于副作用的结构。

这并不是说这是一件不好的事情——只是思考决定背后的哲学原因。


更新7/17/2012:显然,从C 5.0开始,下面描述的foreach的行为已经改变,"在嵌套lambda表达式中使用foreach迭代变量不再产生意想不到的结果。"该答案不适用于C≥5.0。

@约翰·斯基特和所有喜欢foreach关键字的人。

在5.0之前,c中"foreach"的问题在于,它与其他语言中等效的"for understruction"如何工作以及我希望它如何工作不一致(此处所述的个人意见仅因为其他人提到了他们对可读性的意见)。查看有关"访问修改后的闭包"的所有问题以及"关闭循环变量被认为是有害的"。这只是"有害的",因为"foreach"在C中的实现方式。

以下面的例子为例,使用与@fredrik kalseth答案相同的功能等效扩展方法。

1
2
3
4
5
6
7
8
9
10
public static class Enumerables
{
    public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
    {
        foreach (T item in @this)
        {
            action(item);
        }
    }
}

为这个做作过度的例子道歉。我只使用Observable,因为这样做并不牵强。显然,有更好的方法来创建这个可观察的,我只是想证明一点。通常,订阅到Observable的代码是异步执行的,并且可能在另一个线程中执行。如果使用"foreach",这可能会产生非常奇怪和潜在的非确定性结果。

使用"foreach"扩展方法的以下测试通过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[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,9,9>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[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()));
}

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

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


我采用了Fredrik的方法并修改了返回类型。

这样,该方法与其他Linq方法一样支持延迟执行。

编辑:如果不清楚,则此方法的任何用法都必须以tolist()或任何其他方法结束,以强制该方法处理完整的可枚举项。否则,将不执行该操作!

1
2
3
4
5
6
7
8
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach (T item in enumeration)
    {
        action(item);
        yield return item;
    }
}

下面的测试有助于了解它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[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枚举。


别让你的副作用影响到我

I'd like to do the equivalent of the following in LINQ, but I can't figure out how:

< /块引用>

正如其他人在国内外所指出的,LINQ和IEnumerable方法有望不产生副作用。

您真的想对IEnumerable中的每个项"做些什么"吗?那么foreach是最好的选择。当副作用在这里发生时,人们并不感到惊讶。

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

我打赌你不想要副作用

但是根据我的经验,通常不需要副作用。通常会有一个简单的LINQ查询等待发现,伴随着一个stackoverflow.com的答案,由jon skeet、eric lippert或marc gravell解释如何做你想要的!

一些实例

如果您实际上只是聚合(累积)某个值,那么应该考虑使用Aggregate扩展方法。

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

也许您希望从现有值创建一个新的IEnumerable

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

或者您可能想创建一个查找表:

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

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


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

1
2
3
4
5
6
7
8
9
10
11
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;
        }
    }
}

微软有一个实验性的linq交互扩展版本(同样在nuget上,更多链接请参见rxteams的简介)。第9频道的视频很好地解释了这一点。

其文档仅以XML格式提供。我已经在Sandcastle中运行了这个文档,以使其具有更可读的格式。解压文档存档并查找index.html。

在许多其他优点中,它提供了预期的foreach实现。它允许您编写这样的代码:

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

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


根据plinq(从.NET 4.0开始提供),您可以

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

在IEnumerable上执行并行foreach循环。


前臂的作用是引起副作用。IEnumerable用于对集合的惰性枚举。

这种概念上的差异在你考虑的时候是非常明显的。

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

除非您在上面执行"count"或"tolist()"或其他操作,否则不会执行此操作。它显然不是表达出来的。

您应该使用IEnumerable扩展来设置迭代链,并根据各自的源和条件定义内容。表达树是强大和有效的,但你应该学会欣赏它们的本质。并且不仅仅是为了在它们周围编程以保存几个字符来覆盖懒惰的评估。


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

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

短而简单(st)。


正如许多答案已经指出的那样,您可以很容易地自己添加这样的扩展方法。但是,如果您不想这样做,尽管我在BCL中不知道这样的事情,但是在System名称空间中仍然有一个选项,如果您已经有了对反应性扩展的引用(如果您没有,您应该有):

1
2
3
using System.Reactive.Linq;

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

虽然方法名称有点不同,但最终结果正是您要查找的结果。


现在我们可以选择……

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

当然,这打开了一个全新的丝虫罐头。

PS(抱歉字体,这是系统决定的)


受乔恩·斯基特的启发,我将他的解决方案扩展如下:

扩展方法:

1
2
3
4
5
6
7
8
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);
    }
}

客户:

1
2
3
4
5
6
7
8
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);

...

1
2
3
4
    public void ApplyFilter(string filterId)
    {
        Debug.WriteLine(filterId);
    }


前臂也可以用铁链锁住,动作后再放回桩线上。保持流利

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();});

实现的迫切版本。

1
2
3
4
5
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
}

编辑:一个懒惰的版本正在使用yield-return,就像这样。

1
2
3
4
5
6
7
8
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的精彩评论。

1
2
3
4
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()都保存在我的库中。


对于vb.net,应使用:

1
listVariable.ForEach(Sub(i) i.Property ="Value")


为了保持流利,人们可以使用这样的技巧:

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

另一个ForEach例子

1
2
3
4
5
6
7
8
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;
}


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

考虑以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
   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);
      }
   }

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


Morelinq有IEnumerable.ForEach和大量其他有用的扩展。仅仅对ForEach来说,这种依赖可能不值得,但是其中有很多有用的东西。

https://www.nuget.org/packages/morelinq/

https://github.com/morelinq/morelinq


这种"功能性方法"的抽象泄漏了大量时间。在语言层面上没有任何东西可以防止副作用。只要可以让它为容器中的每个元素调用lambda/委托,就可以获得"foreach"行为。

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

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

1
2
3
4
5
6
7
var b = srcDictionary.Select(
                             x=>
                                {
                                  destDictionary[x.Key] = x.Value;
                                  return true;
                                }
                             ).Count();


如果您这样做,例如因为您在迭代中需要索引,您可以始终使用一个where构造:

1
2
3
4
linqObject.Where((obj, index) => {
  DoWork(obj, index);
  return true;
}).ToArray(); //MUST CALL ToArray() or ToList() or something to execute the lazy query, or the loop won't actually execute

这样做还有一个好处,即原始数组返回"unchanged"(列表引用的对象是相同的,尽管它们可能没有相同的数据),这在功能/链编程方法(如LINQ)中通常是可取的。