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

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

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

вопрос задан 19.09.2008
Cameron MacFarland
51000 репутация

20 ответов


  • 171 рейтинг

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

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

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

    Вместо:

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

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

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

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

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

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

    ответ дан Coincoin, с репутацией 20606, 19.09.2008
  • 65 рейтинг

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

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

    public static void ForEach(
        this IEnumerable source,
        Action action)
    {
        foreach (T element in source) 
            action(element);
    }
    
    ответ дан aku, с репутацией 96429, 19.09.2008
  • 46 рейтинг

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

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

    Плюсы

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

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

    Минусы

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

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

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

    ответ дан Jay Bazuzi, с репутацией 30459, 19.10.2008
  • 27 рейтинг

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

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

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

    ответ дан mancaus, с репутацией 2662, 12.10.2008
  • 16 рейтинг

    Хотя я согласен с тем, что в большинстве случаев лучше использовать встроенную конструкцию foreach, я нахожу использование этого варианта в ForEach & lt; & gt; расширение должно быть немного лучше, чем самому управлять индексом в обычном 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
    
    ответ дан Chris Zwiryk, с репутацией 1261, 19.09.2008
  • 14 рейтинг

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

    профи

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

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

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

    минусы

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

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

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

    ответ дан Jay Bazuzi, с репутацией 30459, 19.10.2008
  • 11 рейтинг

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

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

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

    ответ дан Aaron Powell, с репутацией 20199, 19.09.2008
  • 7 рейтинг

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

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

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

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

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

    ответ дан Scott Dorman, с репутацией 34290, 19.09.2008
  • 6 рейтинг

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

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

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

    Хотелось бы, чтобы его привезли в стандартную библиотеку:

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

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

    ответ дан Martijn, с репутацией 6244, 21.02.2013
  • 4 рейтинг

    @Coincoin

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

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

    и т. Д. , ,

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

    ответ дан Jay Bazuzi, с репутацией 30459, 1.11.2008
  • 3 рейтинг

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

    ответ дан Dave Clausen, с репутацией 839, 1.01.2014
  • 3 рейтинг

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

    ответ дан Neil, с репутацией 5079, 11.11.2009
  • 2 рейтинг

    Я написал сообщение в блоге об этом: http: // blogs. MSDN. ком / kirillosenkov / Архив / 2009/01/31 / Еогеасп. aspx

    Вы можете проголосовать здесь, если хотите увидеть этот метод в. NET 4. 0: http: // connect. Microsoft. ru / Visual Studio / обратная связь / просмотр обратной связи. ASPX? FeedbackID = 279093

    ответ дан Kirill Osenkov, с репутацией 6727, 1.02.2009
  • 2 рейтинг

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

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

    ответ дан Arjun Bhalodiya, с репутацией 41, 12.07.2016
  • 2 рейтинг

    Это я или список & lt; T & gt ;. Foreq в значительной степени устарел от 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. нетто / пост / 0a-для-Еогеасп-forFirst-forLast0a-0a-. aspx для MSIL)

    Тогда в DotNet2. 0 Пришли дженерики и в список. Foreach всегда чувствовал, что я являюсь реализацией шаблона Visitor (см. Шаблоны проектирования от Gamma, Helm, Johnson, Vlissides).

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

    ответ дан user18784, с репутацией 21, 19.09.2008
  • 2 рейтинг

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

    ответ дан leppie, с репутацией 95570, 19.09.2008
  • 2 рейтинг

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

    Seq.iter doSomething myIEnumerable

    ответ дан TraumaPony, с репутацией 7569, 19.09.2008
  • 2 рейтинг

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

    ответ дан Paco, с репутацией 7339, 19.09.2008
  • 1 рейтинг

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

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

    private static IEnumerable ForEach(IEnumerable xs, Action f) {
        foreach (var x in xs) {
            f(x); yield return x;
        }
    }
    
    ответ дан fredefox, с репутацией 192, 17.02.2016
  • 1 рейтинг

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

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

    ответ дан Squirrel, с репутацией 830, 11.11.2008