Почему в IEnumerable нет метода расширения ForEach?

Вдохновленный другим вопросом, спрашивающим о отсутствующей функции Zip :

Почему в classе Enumerable нет метода расширения ForEach ? Или где угодно? Единственным classом, который получает метод ForEach является List . Есть ли причина, по которой она отсутствует (производительность)?

Уже есть заявление foreach, включенное в язык, который делает работу большую часть времени.

Мне бы очень хотелось увидеть следующее:

 list.ForEach( item => { item.DoSomething(); } ); 

Вместо:

 foreach(Item item in list) { item.DoSomething(); } 

Последний более ясен и легче читается в большинстве ситуаций , хотя, возможно, немного больше времени для ввода.

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

Вот основные отличия между заявлением и методом:

  • Проверка типов: foreach выполняется во время выполнения, ForEach () во время компиляции (Big Plus!)
  • Синтаксис вызова делегата действительно намного проще: objects.ForEach (DoSomething);
  • ForEach () может быть прикован цепью: хотя злобность / полезность такой функции открыта для обсуждения.

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

Для LINQ был добавлен метод ForEach. Если вы добавите расширение ForEach, он никогда не будет вызван для экземпляров List из-за ограничений методов расширения. Я думаю, что причина, по которой он не был добавлен, – это не вмешательство в существующее.

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

 public static void ForEach( this IEnumerable source, Action action) { foreach (T element in source) action(element); } 

Вы можете написать этот метод расширения:

 // Possibly call this "Do" IEnumerable Apply (this IEnumerable source, Action action) { foreach (var e in source) { action(e); yield return e; } } 

Pros

Позволяет цепочку:

 MySequence .Apply(...) .Apply(...) .Apply(...); 

Cons

На самом деле это ничего не сделает, пока вы не сделаете что-то, чтобы заставить итерацию. По этой причине его не следует называть .ForEach() . Вы могли бы написать .ToList() в конце, или вы могли бы написать этот метод расширения тоже:

 // possibly call this "Realize" IEnumerable Done (this IEnumerable source) { foreach (var e in source) { // do nothing ; } return source; } 

Это может быть слишком значительным отход от библиотек C # доставки; читатели, которые не знакомы с вашими методами расширения, не будут знать, что делать с вашим кодом.

Обсуждение здесь дает ответ:

Собственно, конкретное обсуждение, которое я наблюдал, фактически зависело от функциональной чистоты. В выражении часто высказываются предположения о отсутствии побочных эффектов. Наличие ForEach специально вызывает побочные эффекты, а не просто смириться с ними. – Кейт Фармер (Партнер)

В основном было принято решение о том, чтобы методы расширения были функционально «чистыми». ForEach будет поощрять побочные эффекты при использовании методов расширения Enumerable, что не было целью.

Хотя я согласен с тем, что в большинстве случаев лучше использовать встроенную конструкцию foreach , я считаю, что использование этого варианта в расширении ForEach <> будет немного лучше, чем сам управлять индексом в обычном foreach :

 public static int ForEach(this IEnumerable list, Action action) { if (action == null) throw new ArgumentNullException("action"); var index = 0; foreach (var elem in list) action(index++, elem); return index; } 

пример

 var people = new[] { "Moe", "Curly", "Larry" }; people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p)); 

Дала бы вам:

 Person #0 is Moe Person #1 is Curly Person #2 is Larry 

Один из .ToList().ForEach(x => ...) – написать .ToList().ForEach(x => ...) .

профи

Легко понять – читателю нужно только знать, что такое корабли с C #, а не какие-либо дополнительные методы расширения.

Синтаксический шум очень мягкий (только добавляет немного экстраординарного кода).

Как правило, это не требует дополнительной памяти, поскольку в .ForEach() для native- .ForEach() потребуется реализовать всю коллекцию.

минусы

Порядок операций не является идеальным. Я предпочел бы реализовать один элемент, затем действовать на нем, а затем повторить. Этот код сначала реализует все элементы, а затем последовательно действует на них.

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

Если перечисление бесконечно (как натуральные числа), вам не повезло.

Я всегда думал, что я сам, поэтому, что я всегда несли это со мной:

 public static void ForEach(this IEnumerable col, Action action) { if (action == null) { throw new ArgumentNullException("action"); } foreach (var item in col) { action(item); } } 

Хороший метод расширения.

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

Методы расширения LINQ все возвращают значение, поэтому они могут быть соединены вместе:

 collection.Where(i => i.Name = "hello").Select(i => i.FullName); 

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

Конкретное утверждение о ForEach заключается в том, что на основе ограничений на методы расширения (а именно, что метод расширения никогда не будет переопределять унаследованный метод с той же сигнатурой ), может возникнуть ситуация, когда пользовательский метод расширения доступен для всех classов, которые являются impelement IEnumerable > кроме списка >. Это может вызвать путаницу, когда методы начинают вести себя по-разному в зависимости от того, вызван ли метод расширения или метод наследования.

Вы можете использовать (привязанный, но лениво оцениваемый). Select , сначала выполните свою операцию, а затем верните личность (или что-то еще, если хотите)

 IEnumerable people = new List(){"alica", "bob", "john", "pete"}; people.Select(p => { Console.WriteLine(p); return p}); 

Вам нужно будет убедиться, что он по-прежнему оценивается, либо с Count() (самая дешевая операция для enums afaik), либо еще одна операция, в которой вы нуждались.

Мне бы очень хотелось, чтобы это привело к стандартной библиотеке:

 static IEnumerable WithLazySideEffect(this IEnumerable src, Action action) { return src.Select(i => { action(i); return i} ); } 

Вышеприведенный код становится people.WithLazySideEffect(p => Console.WriteLine(p)) который фактически эквивалентен foreach, но ленив и цепляется.

@Coincoin

Реальная сила метода расширения foreach предполагает повторное использование Action<> без добавления ненужных методов в ваш код. Скажем, что у вас есть 10 списков, и вы хотите выполнить с ними одну и ту же логику, и соответствующая функция не вписывается в ваш class и не используется повторно. Вместо того, чтобы иметь десять для циклов или общую функцию, которая, очевидно, является помощником, который не принадлежит, вы можете сохранить всю свою логику в одном месте ( Action<> . Таким образом, десятки строк заменяются на

 Action f = { foo }; List1.ForEach(p => f(p)) List2.ForEach(p => f(p)) 

и т.д…

Логика находится в одном месте, и вы не загрязнили свой class.

В 3.5 все методы расширения, добавленные в IEnumerable, существуют для поддержки LINQ (обратите внимание, что они определены в classе System.Linq.Enumerable). В этой статье я объясню, почему foreach не принадлежит LINQ: существующий метод расширения LINQ аналогичен Parallel.For?

Обратите внимание, что MoreLINQ NuGet предоставляет метод расширения ForEach который вы ищете (а также метод Pipe который выполняет делегат и дает результат). Видеть:

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

Вы можете использовать select, когда хотите что-то вернуть. Если вы этого не сделаете, вы можете использовать ToList сначала, потому что вы, вероятно, не хотите ничего изменять в коллекции.

Я написал сообщение в блоге об этом: http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

Вы можете проголосовать здесь, если вы хотите увидеть этот метод в .NET 4.0: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=279093

Является ли это мной или является List .Foreach в значительной степени устарел Linq. Первоначально

 foreach(X x in Y) 

где Y просто должен быть IEnumerable (Pre 2.0) и реализовать GetEnumerator (). Если вы посмотрите на сгенерированный MSIL, вы увидите, что он точно такой же, как

 IEnumerator enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { int i = enumerator.Current; Console.WriteLine(i); } 

(См. http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx для MSIL)

Затем в DotNet2.0 появились Generics и List. Foreach всегда считал меня реализацией шаблона Vistor (см. «Образцы дизайна» Гамма, Хелм, Джонсон, Влиссидес).

Теперь, конечно, в 3.5 мы можем вместо этого использовать Lambda для такого же эффекта, например, пример http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh- мой /

Чтобы использовать Foreach, ваш список должен быть загружен в первичную память. Но из-за ленивой загрузки IEnumerable, они не обеспечивают ForEach в IEnumerable.

Однако при высокой загрузке (используя ToList ()) вы можете загрузить свой список в память и воспользоваться преимуществами ForEach.

Если у вас есть F # (который будет в следующей версии .NET), вы можете использовать

Seq.iter doSomething myIEnumerable

Никто еще не указал, что ForEach приводит к проверке типа времени компиляции, когда проверяется время выполнения ключевого слова foreach.

Сделав некоторый рефакторинг, в котором оба метода были использованы в коде, я предпочитаю .ForEach, поскольку мне приходилось выискивать неудачи тестирования / сбои в работе, чтобы найти проблемы с foreach.

Я хотел бы расширить ответ Аку .

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

 private static IEnumerable ForEach(IEnumerable xs, Action f) { foreach (var x in xs) { f(x); yield return x; } } 
Давайте будем гением компьютера.