我想在LINQ中执行以下等效操作,但我无法确定如何执行:

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

真正的语法是什么?


当前回答

微软发布了一个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));

其他回答

我分别不同意链接扩展方法应该是无副作用的概念(不仅因为它们不是,任何委托都可以执行副作用)。

考虑以下事项:

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

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

2012年7月17日更新:显然,从C#5.0开始,下面描述的foreach的行为已经改变,“在嵌套lambda表达式中使用foreach迭代变量不再产生意外结果。”这个答案不适用于C#≥5.0。

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

5.0之前的C#中的“foreach”的问题是,它与其他语言中等效的“for understanding”的工作方式不一致,也与我期望它的工作方式(此处陈述的个人观点仅是因为其他人提到了他们对可读性的看法)不一致。查看有关“修改关闭的访问”的所有问题以及“关闭被认为有害的循环变量”。这只是“有害的”,因为“foreach”是在C#中实现的。

使用@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

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

MoreLinq有IEnumerable<T>.ForEach和许多其他有用的扩展。仅为ForEach使用依赖关系可能不值得,但其中有很多有用的东西。

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

https://github.com/morelinq/MoreLINQ

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


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
}

编辑:Lazy版本使用的是收益率返回,如下所示。

public static IEnumerable<T> ForEachLazy<T>(this IEnumerable<T> enu, Action<T> action)
{
    foreach (var item in enu)
    {
        action(item);
        yield return item;
    }
}

Lazy版本需要具体化,例如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()都保存在库中。

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

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

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

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