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

вдохновленный другим вопросом, спрашивающим о пропавших без вести :

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

20 ответов


в языке уже есть оператор 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, оно никогда не будет вызываться для экземпляров списка из-за ограничений методов расширения. Я думаю, что причина, по которой он не был добавлен, - это не вмешательство в существующий.

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

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

плюсы

позволяет сцепление:

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# библиотеки; читатели, которые не знакомы с вашими методами расширения, не будут знать, что делать с вашим кодом.


здесь дает ответ:

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

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


хотя я согласен, что лучше использовать встроенный 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> за исключением>. Это может вызвать путаницу, когда методы начинают вести себя по-разному в зависимости от того, вызывается ли метод расширения или метод наследования.


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


@Coincoin

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

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

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

etc...

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


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


отметим, что 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


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

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


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

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


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

Seq.iter doSomething myIEnumerable


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

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


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

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

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