Вопрос

Я бы хотел сделать эквивалент следующего в LINQ, но я не могу понять, как это сделать:

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

Каков реальный синтаксис?

Это было полезно?

Решение

Для каждого расширения 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);
    }
}

Другие советы

Фредрик предоставил исправление, но, возможно, стоит задуматься, почему этого нет в фреймворке для начала.Я полагаю, идея заключается в том, что операторы запросов LINQ должны быть свободны от побочных эффектов, вписываясь в разумно функциональный взгляд на мир.Очевидно, что ForEach - это с точностью до наоборот - a чисто конструкция, основанная на побочных эффектах.

Это не значит, что это плохо - просто подумайте о философских причинах такого решения.

Обновить 7/17/2012:По-видимому, начиная с C # 5.0, поведение foreach описанное ниже было изменено и "использование foreach переменная итерации во вложенном лямбда-выражении больше не приводит к неожиданным результатам." Этот ответ не применим к C # ≥ 5.0.

@Джон Скит и все, кто предпочитает ключевое слово foreach.

Проблема с "foreach" в C# до версии 5.0, заключается в том, что это несовместимо с тем, как эквивалент "для понимания" работает на других языках, и с тем, как я ожидал бы, что он будет работать (личное мнение изложено здесь только потому, что другие упомянули свое мнение относительно удобочитаемости).Ознакомьтесь со всеми вопросами, касающимися "Доступ к измененному закрытию" а также "Закрытие переменной цикла, считающейся вредной".Это только "вредно" из-за способа реализации "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 только потому, что это не совсем надуманно, чтобы сделать что-то подобное.Очевидно, что есть лучшие способы создать это наблюдаемое, я всего лишь пытаюсь продемонстрировать точку зрения.Обычно код, подписанный на 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, 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()));
}

Вы могли бы использовать FirstOrDefault() расширение, которое доступно для IEnumerable<T>.Возвращая false исходя из предиката, он будет выполняться для каждого элемента, но его не будет волновать, что на самом деле он не найдет совпадения.Это позволит избежать ToList() над головой.

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

Я воспользовался методом Фредрика и изменил возвращаемый тип.

Таким образом, метод поддерживает отложенное исполнение как и другие методы 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 перечислять.

Держи свои побочные эффекты подальше от моего списка

Я бы хотел сделать эквивалент следующего в LINQ, но я не могу понять, как это сделать:

Как указывали другие здесь и за рубежом LINQ и IEnumerable ожидается, что методы не будут иметь побочных эффектов.

Вы действительно хотите "что-то сделать" с каждым элементом в IEnumerable?Тогда foreach это лучший выбор.Люди не удивляются, когда здесь возникают побочные эффекты.

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

Держу пари, ты не хочешь побочного эффекта

Однако, по моему опыту, побочные эффекты обычно не требуются.Чаще всего простой запрос LINQ ожидает своего открытия, сопровождаемый StackOverflow.com ответом Джона Скита, Эрика Липперта или Марка Гравелла, объясняющим, как сделать то, что вы хотите!

Несколько примеров

Если вы на самом деле просто агрегируете (накапливаете) некоторое значение, то вам следует рассмотреть Aggregate метод расширения.

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

Возможно, вы хотите создать новый IEnumerable из существующих значений.

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

Или, может быть, вы хотите создать справочную таблицу:

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

Список (каламбур не совсем намеренный) возможностей можно продолжать и продолжать.

Если вы хотите действовать как рулоны перечисления, вы должны выдавать каждый элемент.

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

Microsoft выпустила экспериментальный релиз Интерактивные расширения для LINQ (также в NuGet, видеть Профиль RxTeams для получения дополнительных ссылок).Тот Самый Видео с 9 -го канала это хорошо объясняет.

Его документы предоставляются только в формате XML.Я запустил это документация в замке из песка чтобы он был в более удобочитаемом формате.Распакуйте архив документов и найдите index.html.

Среди многих других преимуществ, он обеспечивает ожидаемую реализацию ForEach.Это позволяет вам писать такой код, как этот:

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

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

Согласно PLINQ (доступен начиная с .Net 4.0), вы можете выполнить

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

чтобы выполнить параллельный цикл foreach для IEnumerable.

Цель ForEach - вызвать побочные эффекты.IEnumerable предназначен для ленивого перечисления набора.

Это концептуальное различие вполне заметно, если вдуматься в него.

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

Это не будет выполняться до тех пор, пока вы не выполните "count" или "ToList()" или что-то в этом роде.Это явно не то, что выражается.

Вам следует использовать расширения IEnumerable для настройки цепочек итераций, определяя контент по их соответствующим источникам и условиям.Деревья выражений мощны и эффективны, но вы должны научиться ценить их природу.И не только для программирования вокруг них, чтобы сохранить несколько символов, переопределяющих отложенную оценку.

Многие люди упоминали об этом, но мне пришлось это записать.Разве это не самое понятное / читабельное?

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

Короткий и простой (st).

Как уже указывают многочисленные ответы, вы можете легко добавить такой метод расширения самостоятельно.Однако, если вы не хотите этого делать, хотя я не знаю ни о чем подобном в BCL, в System пространство имен, если у вас уже есть ссылка на Реактивное Расширение (а если вы этого не сделаете, то должны были сделать):

using System.Reactive.Linq;

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

Хотя имена методов немного отличаются, конечный результат - это именно то, что вы ищете.

Теперь у нас есть возможность...

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

Конечно, это открывает совершенно новую банку с нитевидными червями.

ps (Извините за шрифты, это то, что решила система)

Эта абстракция "функционального подхода" сильно утекает времени.Ничто на языковом уровне не предотвращает побочные эффекты.Пока вы можете заставить его вызывать ваш lambda / делегат для каждого элемента в контейнере - вы будете получать поведение "ForEach".

Вот, например, один из способов объединения srcDictionary в destDictionary (если ключ уже существует - перезаписывает)

это взлом, и его не следует использовать ни в каком производственном коде.

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

Вдохновленный Джоном Скитом, я расширил его решение следующим образом:

Способ расширения:

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

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
}

Редактировать: a Ленивый версия использует yield return, например это.

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() в своей библиотеке.

Я, соответственно, не согласен с мнением о том, что методы расширения ссылок должны быть свободны от побочных эффектов (не только потому, что это не так, любой делегат может выполнять побочные эффекты).

Рассмотрим следующее:

   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 для декодирования значения, определяющего действие, и преобразования его в соответствующий метод.

Для VB.NET вы должны использовать:

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

Чтобы оставаться беглым, можно воспользоваться таким трюком:

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

MoreLINQ имеет IEnumerable<T>.ForEach и масса других полезных расширений.Вероятно, не стоит брать зависимость только для ForEach, но там есть много полезного.

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

https://github.com/morelinq/MoreLINQ

Еще один 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;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top