Почему в IEnumerable отсутствует метод расширения ForEach? - PullRequest
341 голосов
/ 19 сентября 2008

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

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

Ответы [ 21 ]

181 голосов
/ 19 сентября 2008

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

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

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

Вместо:

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

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

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

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

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

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

70 голосов
/ 19 сентября 2008

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

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

public static void ForEach<T>(
    this IEnumerable<T> source,
    Action<T> action)
{
    foreach (T element in source) 
        action(element);
}
48 голосов
/ 19 октября 2008

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

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

Плюсы

Позволяет связывать:

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

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

32 голосов
/ 12 октября 2008

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

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

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

16 голосов
/ 19 сентября 2008

Хотя я согласен с тем, что в большинстве случаев лучше использовать встроенную конструкцию 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
14 голосов
/ 19 октября 2008

Один из обходных путей - написать .ToList().ForEach(x => ...).

профи

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

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

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

против

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

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

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

12 голосов
/ 19 сентября 2008

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

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

Отличный метод расширения.

7 голосов
/ 19 сентября 2008

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

Все методы расширения LINQ возвращают значение, чтобы их можно было объединить в цепочку:

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

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

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

6 голосов
/ 21 февраля 2013

Вы можете использовать (цепочку, но лениво оценивать) 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, но ленив и цепочечен.

4 голосов
/ 02 января 2014

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

...