Foreach vs someList.ForEach() {}

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

Первый тип:

List<string> someList = <some way to init>
foreach(string s in someList) {
   <process the string>
}

Другой способ:

List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
    <process the string>
});

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

Ответ 1

Существует одно важное и полезное различие между ними.

Потому что .ForEach использует цикл for для итерации коллекции, это действительно (edit: до .net 4.5 - реализация изменилась, и оба они выбрасывают):

someList.ForEach(x => { if(x.RemoveMe) someList.Remove(x); }); 

тогда как foreach использует перечислитель, поэтому это неверно:

foreach(var item in someList)
  if(item.RemoveMe) someList.Remove(item);

tl; dr: НЕ копируйте этот код в ваше приложение!

Эти примеры не являются лучшей практикой, они просто демонстрируют различия между ForEach() и foreach.

Удаление элементов из списка в цикле for может иметь побочные эффекты. Наиболее распространенный из них описан в комментариях к этому вопросу.

Как правило, если вы хотите удалить несколько элементов из списка, вы хотите отделить определение того, какие элементы удалить из фактического удаления. Это не делает ваш код компактным, но он гарантирует, что вы не пропустите какие-либо предметы.

Ответ 2

У нас был некоторый код здесь (в VS2005 и С# 2.0), где предыдущие инженеры отказались от использования list.ForEach( delegate(item) { foo;}); вместо foreach(item in list) {foo; }; для всего кода, который они написали. например блок кода для чтения строк из dataReader.

Я до сих пор не знаю, почему они это сделали.

Недостатками list.ForEach() являются:

  • Это более подробно в С# 2.0. Тем не менее, в С# 3, вы можете использовать синтаксис "=>", чтобы сделать несколько красиво сжатых выражений.

  • Это менее знакомо. Люди, которые должны поддерживать этот код, задаются вопросом, почему вы это сделали. Мне потребовалось некоторое время, чтобы решить, что не было никакой причины, кроме, может быть, чтобы писатель казался умным (качество остальной части кода подрывало это). Он также был менее читаемым, с "})" в конце блока кода делегата.

  • См. также книгу Билла Вагнера "Эффективный С#: 50 конкретных способов улучшить ваш С#", где он говорит о том, почему foreach предпочитают другие циклы, например, для циклов while или while, - главное, что вы разрешаете компилятору решить лучший способ построения цикла. Если будущей версии компилятора удастся использовать более быструю технику, вы получите это бесплатно, используя foreach и перестраивая, а не меняя свой код.

    Конструкция
  • a foreach(item in list) позволяет использовать break или continue, если вам нужно выйти из итерации или цикла. Но вы не можете изменить список внутри цикла foreach.

Я удивлен, увидев, что list.ForEach немного быстрее. Но это, вероятно, не является веской причиной для его использования во всем, что было бы преждевременной оптимизацией. Если ваше приложение использует базу данных или веб-службу, которая, а не управление контуром, почти всегда будет там, где время идет. И вы сравнили его с циклом for? list.ForEach может быть быстрее из-за использования этого внутри, а цикл for без оболочки будет еще быстрее.

Я не согласен с тем, что версия list.ForEach(delegate) "более функциональна" любым существенным образом. Он передает функцию функции, но нет большой разницы в результатах или организации программы.

Я не думаю, что foreach(item in list) "точно говорит, как вы этого хотите" - цикл for(int 1 = 0; i < count; i++) делает это, цикл foreach оставляет выбор управления до компилятора.

Я чувствую, что в новом проекте использовать foreach(item in list) для большинства циклов, чтобы придерживаться обычного использования и для удобства чтения, и использовать list.ForEach() только для коротких блоков, когда вы можете сделать что-то более элегантное или компактно с оператором С# 3 "=>". В подобных случаях уже может существовать метод расширения LINQ, который более специфичен, чем ForEach(). Смотрите, если Where(), Select(), Any(), All(), Max() или один из многих других методов LINQ еще не делает то, что вы хотите от цикла.

Ответ 3

List<T>.ForEach() немного быстрее - он обращается к внутреннему массиву по индексу, тогда как оператор foreach использует Enumerator:

  var list1 = Enumerable.Repeat(1,10000000).ToList();
  var list2 = new List<int>(); 
  var list3 = new List<int>();
  var sw    = new System.Diagnostics.Stopwatch();


  sw.Start();
  foreach(var x in list1) list2.Add(x);
  sw.Stop();
  Console.WriteLine(sw.ElapsedMilliseconds);

  sw.Reset();
  sw.Start();
  list1.ForEach(x => list3.Add(x));
  sw.Stop();
  Console.WriteLine(sw.ElapsedMilliseconds);

Приведенный выше тест дает эти ответы, дает или занимает несколько миллисекунд:

280
230

В зависимости от того, как вы меняете тест, различия могут быть больше или меньше, но List<T>.ForEach() последовательно быстрее, даже если это пренебрежимо.

Ответ 4

Для удовольствия я вытащил List в отражатель, и это результат С#:

public void ForEach(Action<T> action)
{
    if (action == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    for (int i = 0; i < this._size; i++)
    {
        action(this._items[i]);
    }
}

Аналогично, MoveNext в Enumerator, который используется foreach, таков:

public bool MoveNext()
{
    if (this.version != this.list._version)
    {
        ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
    }
    if (this.index < this.list._size)
    {
        this.current = this.list._items[this.index];
        this.index++;
        return true;
    }
    this.index = this.list._size + 1;
    this.current = default(T);
    return false;
}

List.ForEach намного более урезан, чем MoveNext - гораздо меньше обработки - более вероятно, что JIT станет чем-то эффективным.

Кроме того, foreach() будет выделять новый Enumerator независимо от того, что. GC - ваш друг, но если вы делаете один и тот же foreach повторно, это будет делать больше отброшенных объектов, в отличие от повторного использования одного и того же делегата - НО - это действительно случайный случай. В типичном использовании вы будете видеть мало или вообще не иметь разницы.

Ответ 5

Я знаю две неясные вещи, которые делают их разными. Иди меня!

Во-первых, существует классическая ошибка создания делегата для каждого элемента в списке. Если вы используете ключевое слово foreach, все ваши делегаты могут обратиться к последнему элементу списка:

    // A list of actions to execute later
    List<Action> actions = new List<Action>();

    // Numbers 0 to 9
    List<int> numbers = Enumerable.Range(0, 10).ToList();

    // Store an action that prints each number (WRONG!)
    foreach (int number in numbers)
        actions.Add(() => Console.WriteLine(number));

    // Run the actions, we actually print 10 copies of "9"
    foreach (Action action in actions)
        action();

    // So try again
    actions.Clear();

    // Store an action that prints each number (RIGHT!)
    numbers.ForEach(number =>
        actions.Add(() => Console.WriteLine(number)));

    // Run the actions
    foreach (Action action in actions)
        action();

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

(К сожалению, я считаю, что ForEach является членом List, а не методом расширения, хотя его легко определить самостоятельно, поэтому у вас есть это средство для любого перечислимого типа.)

Во-вторых, подход метода ForEach имеет ограничение. Если вы используете IEnumerable, используя возврат доходности, вы не можете сделать возврат доходности внутри лямбда. Таким образом, цикл с помощью элементов в коллекции, чтобы возвращать вещи, невозможно с помощью этого метода. Вам нужно будет использовать ключевое слово foreach и решить проблему замыкания, вручную создав копию текущего значения цикла внутри цикла.

Подробнее здесь

Ответ 6

Я предполагаю, что вызов someList.ForEach() может быть легко распараллелен, тогда как обычный foreach не так просто запустить параллельно. Вы можете легко запустить несколько разных делегатов на разных ядрах, что не так просто сделать с обычным foreach. Только мои 2 цента

Ответ 7

Вы можете назвать анонимного делегата: -)

И вы можете написать второй как:

someList.ForEach(s => s.ToUpper())

Что я предпочитаю и экономят много ввода.

Как говорит Йоахим, parallelism легче применить ко второй форме.

Ответ 8

List.ForEach() считается более функциональным.

List.ForEach() говорит, что вы хотите сделать. foreach(item in list) также точно описывает, как вы это хотите. Это позволяет List.ForEach свободно изменять реализацию как часть в будущем. Например, гипотетическая будущая версия .Net может всегда запускать List.ForEach параллельно, исходя из предположения, что на данный момент у каждого есть несколько ядер процессора, которые обычно сидят без дела.

С другой стороны, foreach (item in list) дает вам немного больше контроля над циклом. Например, вы знаете, что элементы будут повторяться в каком-то последовательном порядке, и вы можете легко сломать середину, если элемент удовлетворяет некоторому условию.

Ответ 9

Рассмотрим следующую статью о производительности list.foreach.

Ответ 10

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

Таким образом, у вас есть другой вызов делегата (= метод).

Кроме того, существует возможность перебора списка с циклом for.

Ответ 11

За кулисами анонимный делегат превращается в фактический метод, поэтому у вас могут быть некоторые накладные расходы со вторым выбором, если компилятор не выбрал встроенную функцию. Кроме того, любые локальные переменные, на которые ссылается тело анонимного делегата, будут меняться по своей природе из-за трюков компилятора, чтобы скрыть тот факт, что он скомпилирован в новый метод. Подробнее о том, как С# делает эту магию:

http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx

Ответ 12

Остерегайтесь того, как выйти из общего метода .ForEach - см. это обсуждение. Хотя ссылка, похоже, говорит, что этот способ является самым быстрым. Не знаете почему - вы думаете, что они будут эквивалентны после компиляции...

Ответ 13

Функция ForEach является членом общего класса List.

Я создал следующее расширение для воспроизведения внутреннего кода:

public static class MyExtension<T>
    {
        public static void MyForEach(this IEnumerable<T> collection, Action<T> action)
        {
            foreach (T item in collection)
                action.Invoke(item);
        }
    }

Итак, в конце мы используем обычный foreach (или цикл, если хотите).

С другой стороны, использование функции делегата - это еще один способ определить функцию, этот код:

delegate(string s) {
    <process the string>
}

эквивалентно:

private static void myFunction(string s, <other variables...>)
{
   <process the string>
}

или используя выражения labda:

(s) => <process the string>

Ответ 14

Вся область ForEach (функция делегата) рассматривается как одна строка кода (вызов функции), и вы не можете установить точки останова или шаг в код. Если возникает необработанное исключение, весь блок отмечен.