LINQ эквивалент foreach для IEnumerable <T>


716

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

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

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


99
Рассмотрим «foreach» против «ForEach» Эрика Липперта. Это дает хорошее обоснование для того, чтобы не использовать / поставлять ForEach().

4
@pst: раньше я слепо придерживался методов расширения в своих проектах. Спасибо за эту статью.
Робин Мабен

2
Есть MoreLINQ, который имеет ForEachрасширение .
Уве Кейм

2
Хотя это и не очень сексуально, это помещается на одной строке без создания копии через ToList -foreach (var i in items) i.Dostuff();
Джеймс Вестгейт

1
Некоторые из этих ссылок мертвы! За какую статью было так много проголосовано!
Уоррен

Ответы:


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

15
Есть несколько причин, почему «.Foreach ()» будет лучше. 1. Многопоточность, возможно с помощью PLINQ. 2. Исключения: вы можете собирать все исключения в цикле и генерировать их сразу; и они являются кодами шума.
Деннис C

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; }
Кис С. Баккер

22
Обратите внимание, что в первом случае разработчик 1) практически не экономит набор текста и 2) излишне выделяет память для хранения всей последовательности в виде списка, прежде чем выбросить ее. Подумайте, прежде чем LINQ
Марк Соул

364

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

Нельзя сказать, что это плохая вещь - просто думать о философских причинах решения.


44
И еще, у F # есть Seq.iter
Стивен Свенсен

6
Однако есть одна очень полезная вещь, которую функции LINQ обычно предоставляют, что foreach () не делает - анонимная функция, взятая функциями Linq, также может принимать индекс в качестве параметра, тогда как с foreach вы должны перестраивать классическую форму для (int i .. ) построить. И функциональные языки должны допускать итерацию, которая имеет побочные эффекты - иначе вы никогда не сможете поработать с ними ни на йоту :) Я хотел бы отметить, что типичным функциональным методом было бы возвращать исходное перечисляемое значение без изменений.
Уолт W

3
@Walt: Я все еще не уверен, что согласен, отчасти потому, что типичный действительно функциональный подход не будет использовать foreach как этот ... он будет использовать функциональный подход, более похожий на LINQ, создавая новую последовательность.
Джон Скит

12
@Stephen Swensen: да, F # имеет Seq.iter, но F # также имеет неизменные структуры данных. при использовании Seq.iter на них оригинальный Seq не изменяется; новый Seq с побочными элементами возвращается.
Андерс

7
@Anders - Seq.iterничего не возвращает, не говоря уже о новом seq с побочными эффектами. Это действительно только побочные эффекты. Вы можете подумать Seq.map, потому что Seq.iterэто точно эквивалентно ForEachметоду расширения на IEnumerabe<_>.
Джоэл Мюллер

39

Обновление от 17.07.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, потому что делать что-то подобное не так уж и сложно. Очевидно, есть лучшие способы создать эту наблюдаемую, я только пытаюсь продемонстрировать точку. Обычно код, подписанный на наблюдаемое, выполняется асинхронно и, возможно, в другом потоке. Если использовать «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, 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 # 7 или 8 лет, с тех пор как был выпущен C # 5.0, что изменило поведение, описанное здесь. В то время, когда он дал это предупреждение 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();}вас взяли больше персонажей и сделали это крайне запутанным
Марк Соул

14
Хорошо, но, возможно, более читабельный хак:items.All(i => { i.DoStuff(); return true; }
nawfal

5
Я знаю, что это довольно старый пост, но я должен был также понизить его. Я согласен, что это умный трюк, но он не поддается ремонту. На первый взгляд кажется, что он что-то делает только с первым элементом в списке. Это может легко вызвать больше ошибок в будущем из-за неправильного понимания того, как он функционирует и что он должен делать.
Ли

4
Это побочный эффект программирования и ИМХО не рекомендуется.
Аниш

21

Я взял метод Фредрика и изменил тип возвращаемого значения.

Таким образом, метод поддерживает отложенное выполнение, как и другие методы 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, ForEachэквивалент в коллекциях Scala, даже ленивых, также имеет тип возвращаемого значения, эквивалентный void в C #.
drstevens

Это лучший ответ: отложенное исполнение + свободный API.
Александр Данилов

3
Это работает точно так же, как звонить Selectс ToList. Цель ForEachсостоит в том, чтобы не звонить ToList. Это должно быть выполнено немедленно.
t3chb0t

3
Какая польза от этого ForEach, выполнение которого откладывается, по сравнению с тем, который выполняется немедленно? Отсрочка (что неудобно в том, как будет использоваться ForEach) не уменьшает тот факт, что ForEach выполняет полезную работу, только если у Action есть побочные эффекты [обычная жалоба на такой оператор Linq]. Итак, как же это изменение помощи? Кроме того, добавленный «ToList ()» для принудительного выполнения не имеет никакой полезной цели. Это несчастный случай, ожидающий случиться. Смысл «ForEach» - это его побочные эффекты. Если вы ХОТИТЕ возвращенное перечисление, SELECT имеет больше смысла.
ToolmakerSteve

20

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

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

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

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

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

Бьюсь об заклад, вы не хотите побочный эффект

Однако по моему опыту побочные эффекты обычно не требуются. Чаще всего существует простой запрос LINQ, ожидающий своего открытия, сопровождаемый ответом StackOverflow.com от Jon Skeet, Eric Lippert или Marc Gravell, объясняющих, как делать то, что вы хотите!

Некоторые примеры

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

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

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

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

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

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

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


7
Ах, так что Select это Map, а Aggregate is Reduce.
Даррел Ли

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 Interactive Extensions для 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
Maciek Świszczowski

8

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

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

сделать параллельный цикл foreach на IEnumerable.


пока ваши действия безопасны для потоков, все хорошо. но что произойдет, если у вас будет не-безопасное действие? это может привести к хаосу ...
shirbr510

2
Правда. Это не потокобезопасно, из некоторого пула потоков будут использоваться разные потоки ... И мне нужно пересмотреть мой ответ. ForEach () не существует, когда я пытаюсь сделать это сейчас. Должно быть, это был мой код, содержащий расширение с ForEach, когда я размышлял об этом.
Wolf5

6

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

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

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

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

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


5

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

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

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


Вы можете удалить всю первую строку и использовать GetItems непосредственно в foreach
tymtam

3
Конечно. Это написано так для ясности. Так что понятно, какой GetItems()метод возвращает.
Ненад

4
Теперь, когда я читаю мой комментарий, я вижу, что я говорю вам то, чего вы не знаете. Я не это имел ввиду. Я имел в виду, чтоforeach (var item in GetItems()) item.DoStuff(); это просто красота.
тымтам

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 , просто положить обратно в pileline после действия. оставаться в курсе


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
}

Edit: Ленивая версия использует возврат доходности, как это .

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); }? Единственный явный цикл. Я думаю, было ли важно сделать все действия action1 ДО того, как начать делать action2.
ToolmakerSteve

@ Rm558 - ваш подход с использованием «доходности». Pro: только один раз перечисляет исходную последовательность, поэтому корректно работает с более широким диапазоном перечислений. Con: если вы забудете ".ToArray ()" в конце, ничего не произойдет. Хотя провал будет очевидным, не упускать из виду надолго, так что не большие расходы. Мне не кажется «чистым», но это правильный компромисс, если вы идете по этому пути (оператор ForEach).
ToolmakerSteve

1
Этот код не является безопасным с точки зрения транзакций. Что делать, если это не удается ProcessShipping()? Вы отправляете электронное письмо покупателю, снимаете деньги с его кредитной карты, но никогда не отправляете ему его вещи? Конечно, просят проблемы.
zmechanic

2

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

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

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

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; }? Если нет в рабочем коде, где и почему вы должны когда-либо использовать это?
Марк Совул

3
Это просто показать, что это возможно в принципе. Это также делает то, о чем просил OP, и я четко указал, что его не следует использовать в производстве, все еще любопытно и имеет образовательную ценность.
Зар Шардан

2

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

Метод продления:

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

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


9
Есть разница между тем, может ли метод расширения иметь побочные эффекты и ДОЛЖЕН ли он иметь. Вы только указываете, что это может иметь место, хотя могут быть очень веские основания предполагать, что они не должны иметь побочных эффектов. В функциональном программировании все функции не имеют побочных эффектов, и при использовании конструкций функционального программирования вы можете предположить, что они есть.
Дейв Ван ден Эйнде

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 для добавления в список. Почему это не просто возвращает адреса. Выберите (a => MapToDomain (a))?
Марк Совул
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.