Выполнение определенного действия для всех элементов в Enumerable<T>

StackOverflow https://stackoverflow.com/questions/529188

  •  22-08-2019
  •  | 
  •  

Вопрос

у меня есть 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; }), но он ничего не печатал.

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

Решение

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

Names.ToList().ForEach(e => ...);

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

Вы ищете вечно неуловимый 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() для проверки предиката или действия?).

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


Первое, что здесь нужно понять, это операции C# linq, такие как Select(), All(), Where(), и т. д., имеют свои корни в функциональное программирование.Идея заключалась в том, чтобы перенести некоторые наиболее полезные и доступные части функционального программирования в мир .Net.Это важно, поскольку ключевым принципом функционального программирования является отсутствие побочных эффектов в операциях.Это трудно недооценить.Однако в случае ForEach()/each(), Побочные эффекты – вот вся цель операции.Добавление 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 ключевое слово из операций в цикле.Здесь также указана команда цикла прежний к операциям, которые определяют последовательность, в которой работает цикл.Гораздо естественнее, чтобы сначала выполнялись эти операции, а затем команда цикла. конец определения запроса.Кроме того, код просто уродлив.Кажется, было бы гораздо приятнее написать вот это:

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. Это не лень.Эти операции происходят немедленно.Это не позволяет мне, скажем, создать форму, в которой пользователь выбирает операцию как одно поле на большом экране, над которым не выполняются действия, пока пользователь не нажмет командную кнопку.Эта форма может позволить пользователю передумать перед выполнением команды.Это совершенно нормально(легкий даже) с запросом 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(), вы все равно можете избежать (видимых программой) побочных эффектов, возможно, проецируя их на анонимный тип.

К сожалению, в текущей версии 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);
  }
}

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

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

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

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

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

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

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

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

В списке есть метод 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;
    }
}

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top