foreach vs someList.ForEach () {} - PullRequest
       34

foreach vs someList.ForEach () {}

139 голосов
/ 22 октября 2008

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

Первый тип:

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

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

Ответы [ 12 ]

115 голосов
/ 22 октября 2008

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

Поскольку .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 может иметь побочные эффекты. Наиболее распространенный из них описан в комментариях к этому вопросу.

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

71 голосов
/ 22 октября 2008

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

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

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

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

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

  • См. Также книгу Билла Вагнера «Эффективный C #: 50 конкретных способов улучшить ваш C #», где он рассказывает о том, почему foreach предпочтительнее других циклов, таких как циклы for или 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() только для коротких блоков, когда вы можете сделать что-то более элегантно или компактно с оператором C # 3 "=>". В подобных случаях уже может существовать метод расширения LINQ, более конкретный, чем ForEach(). Посмотрите, если Where(), Select(), Any(), All(), Max() или один из многих других методов LINQ уже не делают то, что вы хотите из цикла.

16 голосов
/ 22 октября 2008

Ради интереса я вытолкнул Список в отражатель, и вот результирующий C #:

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 () будет выделять новый перечислитель, несмотря ни на что. GC - ваш друг, но если вы неоднократно выполняете один и тот же foreach, это создаст больше одноразовых объектов, в отличие от повторного использования одного и того же делегата - НО - это действительно незначительный случай. При обычном использовании вы увидите небольшую разницу или ее нет.

11 голосов
/ 22 октября 2008

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

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

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

10 голосов
/ 22 октября 2008

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

3 голосов
/ 22 октября 2008

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

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

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

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

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

2 голосов
/ 31 марта 2016

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

2 голосов
/ 22 октября 2008

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

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

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

2 голосов
/ 22 октября 2008

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

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

1 голос
/ 25 апреля 2014

Функция 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>
...