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

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

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

Ответы

Ответ 1

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

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

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

Вместо:

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

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

Pros

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

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

против

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

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

    return source;
}

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

Ответ 4

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

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

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

Ответ 5

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

public static int ForEach<T>(this IEnumerable<T> list, Action<int, T> 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

Ответ 6

Одним из способов решения проблемы является запись .ToList().ForEach(x => ...).

профи

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

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

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

против

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

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

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

Ответ 7

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

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

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

Ответ 8

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

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

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

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

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

Ответ 9

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

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

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

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

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

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

Ответ 10

@Coincoin

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

Action<blah,blah> f = { foo };

List1.ForEach(p => f(p))
List2.ForEach(p => f(p))

и т.д...

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

Ответ 13

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

Ответ 14

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

Seq.iter doSomething myIEnumerable

Ответ 15

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

foreach(X x in Y) 

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

IEnumerator<int> 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 (см. "Образцы дизайна" от Gamma, Helm, Johnson, Vlissides).

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

Ответ 16

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

Ответ 18

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

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

Ответ 19

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

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

Ответ 20

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

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

private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
    foreach (var x in xs) {
        f(x); yield return x;
    }
}