Выполнение определенного действия для всех элементов в Enumerable <T> - PullRequest
44 голосов
/ 09 февраля 2009

У меня есть Enumerable<T> и я ищу метод, позволяющий мне выполнить действие для каждого элемента, вроде Select, но затем для побочных эффектов. Что-то вроде:

string[] Names = ...;
Names.each(s => Console.Writeline(s));

или

Names.each(s => GenHTMLOutput(s));   
// (where GenHTMLOutput cannot for some reason receive the enumerable itself as a parameter)

Я попробовал Select(s=> { Console.WriteLine(s); return s; }), но ничего не печаталось.

Ответы [ 9 ]

51 голосов
/ 09 февраля 2009

Быстрый и простой способ получить это:

Names.ToList().ForEach(e => ...);
18 голосов
/ 09 февраля 2009

Вы ищете неуловимый ForEach, который в настоящее время существует только в общей коллекции List. В Интернете много дискуссий о том, должна ли Microsoft добавлять или не добавлять это как метод LINQ. В настоящее время вы должны бросить свои собственные:

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

Хотя метод All() предоставляет аналогичные возможности, он используется для выполнения теста предиката для каждого элемента, а не для действия. Конечно, его можно убедить выполнять другие задачи, но это несколько меняет семантику и усложнит для других интерпретацию вашего кода (т.е. это использование All() для теста предикатов или действия?).

8 голосов
/ 09 февраля 2009

Отказ от ответственности: Этот пост больше не похож на мой первоначальный ответ, а скорее включает в себя около семи лет опыта, который я получил с тех пор. Я сделал правку, потому что это очень популярный вопрос, и ни один из существующих ответов не охватил все аспекты. Если вы хотите увидеть мой оригинальный ответ, он доступен в истории изменений для этого поста .


Первое, что нужно понять, - это такие операции C # linq, как Select(), All(), Where() и т. Д., Корни которых лежат в функциональном программировании . Идея заключалась в том, чтобы перенести некоторые из наиболее полезных и доступных частей функционального программирования в мир .Net. Это важно, потому что ключевым принципом функционального программирования является то, что эти операции не должны иметь побочных эффектов. Трудно преуменьшить это. Добавление операций each() или ForEach() не только выходит за рамки функционального программирования других операторов linq, но и прямо противоположно им.

Но я понимаю, что это неудовлетворительно. У вас есть реальная проблема, которую вам нужно решить. Почему вся эта философия башни из слоновой кости должна мешать чему-то, что может быть действительно полезным?

Эрик Липперт, который в то время был в команде разработчиков C #, может помочь нам здесь. Он рекомендует просто использовать традиционную петлю foreach:

[ForEach ()] добавляет ноль новых представительных возможностей в язык. Это позволяет вам переписать этот совершенно чистый код:

foreach(Foo foo in foos){ statement involving foo; }

в этот код:

foos.ForEach(foo=>{ statement involving foo; });

Суть в том, что если вы внимательно посмотрите на параметры синтаксиса, вы не получите ничего нового от расширения ForEach() по сравнению с традиционным циклом foreach. Я частично не согласен. Представьте, что у вас есть это:

 foreach(var item in Some.Long(and => possibly)
                         .Complicated(set => ofLINQ)
                         .Expression(to => evaluate)
 {
     // now do something
 } 

Этот код скрывает значение, потому что он отделяет ключевое слово foreach от операций в цикле. В нем также перечислены команды цикла before для операций, которые определяют последовательность, в которой работает цикл. Гораздо более естественно хотеть, чтобы сначала выполнялись эти операции, а затем команда loop в end определения запроса. Кроме того, код просто ужасен. Кажется, что было бы намного лучше написать это:

Some.Long(and => possibly)
   .Complicated(set => ofLINQ)
   .Expression(to => evaluate)
   .ForEach(item => 
{
    // now do something
});

Однако даже здесь я в конце концов пришел к точке зрения Эрика. Я понял, что код, который вы видите выше, вызывает дополнительную переменную. Если у вас есть сложный набор выражений LINQ, подобных этому, вы можете добавить некоторую ценную информацию в свой код, сначала присвоив результат выражения LINQ новой переменной:

var queryForSomeThing = Some.Long(and => possibly)
                        .Complicated(set => ofLINQ)
                        .Expressions(to => evaluate);
foreach(var item in queryForSomeThing)
{
    // now do something
}

Этот код кажется более естественным. Ключевое слово foreach помещается рядом с остальной частью цикла и после определения запроса. Более того, имя переменной может добавить новую информацию, которая будет полезна будущим программистам, пытающимся понять цель запроса LINQ. Опять же, мы видим, что требуемый оператор ForEach() действительно не добавил новой выразительной силы в язык.

Однако нам все еще не хватает двух возможностей гипотетического метода расширения ForEach():

  1. Это не составно. Я не могу добавить еще .Where() или GroupBy() или OrderBy() после цикла foreach, встроенного в остальную часть кода, без создания нового оператора.
  2. Это не ленивый. Эти операции происходят немедленно. Это не позволяет мне, скажем, иметь форму, где пользователь выбирает операцию в качестве одного поля на большом экране, на который не воздействуют, пока пользователь не нажмет командную кнопку. Эта форма может позволить пользователю передумать перед выполнением команды. Это совершенно нормально ( easy даже) с запросом LINQ, но не так просто с foreach.

Конечно, вы можете создать свой собственный ForEach() метод расширения. Несколько других ответов уже имеют реализации этого метода; это не все так сложно. Тем не менее, я чувствую, что это не нужно. Уже существует существующий метод, который соответствует тому, что мы хотим сделать как с семантической, так и с операционной точек зрения. Обе вышеуказанные недостающие функции могут быть решены с помощью существующего оператора Select().

Select() соответствует виду преобразования или проекции, описанному в обоих приведенных выше примерах. Имейте в виду, однако, что я все равно избегал бы создания побочных эффектов. Вызов Select() должен вернуть либо новые объекты, либо проекции оригиналов. Иногда этому может помочь использование анонимного типа или динамического объекта (если и только если это необходимо). Если вам нужно, чтобы результаты сохранялись, скажем, в исходной переменной списка, вы всегда можете вызвать .ToList() и присвоить ее исходной переменной. Здесь я добавлю, что мне больше нравится работать с переменными IEnumerable<T>, чем с более конкретными типами.

myList = myList.Select(item => new SomeType(item.value1, item.value2 *4)).ToList();

В итоге:

  1. Просто придерживайтесь foreach большую часть времени.
  2. Когда foreach действительно не пойдет (что, вероятно, не так часто, как вы думаете), используйте Select()
  3. Когда вам нужно использовать Select(), вы все равно можете избежать (видимых программой) побочных эффектов, возможно, проецируя на анонимный тип.
6 голосов
/ 09 февраля 2009

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

http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

Хотя добавить его довольно просто.

public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action) {
  foreach ( var cur in enumerable ) {
    action(cur);
  }
}
4 голосов
/ 09 февраля 2009

Вы не можете сделать это сразу с LINQ и IEnumerable - вам нужно либо реализовать свой собственный метод расширения, либо привести свое перечисление к массиву с помощью LINQ, а затем вызвать Array.ForEach ():

Array.ForEach(MyCollection.ToArray(), x => x.YourMethod());

Обратите внимание, что из-за того, как работают типы значений и структуры, если коллекция относится к типу значений и вы таким образом изменяете элементы коллекции, это не повлияет на элементы исходной коллекции.

2 голосов
/ 09 февраля 2009

Поскольку LINQ разработан как функция запроса, а не функция обновления, вы не найдете расширения, которое выполняет методы в IEnumerable , поскольку это позволит вам выполнить метод (возможно, с побочными эффектами). В этом случае вы можете просто придерживаться

foreach (имя строки в Именах)
Console.WriteLine (имя);

1 голос
/ 21 мая 2015

Ну, вы также можете использовать стандартное ключевое слово foreach, просто отформатируйте его в oneliner:

foreach(var n in Names.Where(blahblah)) DoStuff(n);

Извините, думал, что этот вариант заслуживает быть здесь:)

0 голосов
/ 29 октября 2015

Использование Parallel Linq:

Names.AsParallel (). ForAll (name => ...)

0 голосов
/ 09 февраля 2009

Существует метод ForEach вне списка. Вы можете преобразовать Enumerable в List, вызвав метод .ToList (), а затем вызвать метод ForEach.

Кроме того, я слышал о людях, определяющих свой собственный метод ForEach из IEnumerable. Это можно сделать, по сути, вызвав метод ForEach, но вместо этого поместив его в метод расширения:

public static class IEnumerableExtensions
{
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> _this, Action<T> del)
    {
        List<T> list = _this.ToList();
        list.ForEach(del);
        return list;
    }
}
...