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

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

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

22 ответа

Решение

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

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

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

Вместо:

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

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

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

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

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

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

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

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

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

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

// 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(...);

Cons

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

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

    return source;
}

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

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

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

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

Хотя я согласен, что лучше использовать встроенный 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

Один обходной путь должен написать .ToList().ForEach(x => ...),

профи

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

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

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

минусы

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

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

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

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

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

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

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

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

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

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

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

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

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

Частично это потому, что дизайнеры языка не согласны с этим с философской точки зрения.

  • Отсутствие (и тестирование...) функции - это меньше работы, чем наличие функции.
  • На самом деле он не короче (есть несколько случаев, когда это происходит, но это не будет основным использованием).
  • Его цель - побочные эффекты, а это не то, о чем linq.
  • Почему есть другой способ сделать то же самое в качестве функции, которую мы уже получили? (ключевое слово foreach)

https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/

@Coincoin

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

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

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

так далее...

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

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

Seq.iter doSomething myIEnumerable

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

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

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

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

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

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

Это я или это List. Потому что Линк почти устарел. Первоначально там было

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 и список.Foreach всегда чувствовал, что я являюсь реализацией шаблона Vistor (см. Шаблоны проектирования от Gamma, Helm, Johnson, Vlissides).

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

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

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

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

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

Моя версия - это метод расширения, который позволит вам использовать ForEach в IEnumerable of T

      public static class EnumerableExtension
{
        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        source.All(x =>
        {
            action.Invoke(x);
            return true;
        });
    }
}

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

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

Другие вопросы по тегам