Еогеасп против someList.ForEach () {}

голоса
118

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

Первый тип:

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

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

Задан 22/10/2008 в 15:19
источник пользователем
На других языках...                            


13 ответов

голоса
97

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

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

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

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

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

ТЛ; др: НЕ CopyPaste этот код в ваше приложение!

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

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

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

Ответил 22/10/2008 в 15:50
источник пользователем

голоса
56

Мы имели некоторый код здесь (в 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(item in list)конструкция позволяет использовать breakили , continueесли вам нужно выйти итерацию или цикл. Но вы не можете изменить список внутри цикла Еогеаспа.

Я удивлен, что 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 уже не делать то , что вы хотите от петли.

Ответил 22/10/2008 в 16:28
источник пользователем

голоса
15

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

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 во что-то эффективное ..

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

Ответил 22/10/2008 в 15:53
источник пользователем

голоса
11

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

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

    // 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 является членом списка, а не метод расширения, хотя это легко определить его самостоятельно, так что вы имеете этот объект на любом перечислимого типа.)

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

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

Ответил 22/10/2008 в 16:41
источник пользователем

голоса
10

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

Ответил 22/10/2008 в 15:22
источник пользователем

голоса
3

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

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

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

Ответил 22/10/2008 в 15:41
источник пользователем

голоса
3

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

Ответил 22/10/2008 в 15:35
источник пользователем

голоса
3

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

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

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

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

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

Ответил 22/10/2008 в 15:24
источник пользователем

голоса
1

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

Ответил 31/03/2016 в 06:49
источник пользователем

голоса
1

Функция ForEach является членом списка родового класса.

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

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

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

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

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

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

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

или с помощью labda выражений:

(s) => <process the string>
Ответил 25/04/2014 в 11:50
источник пользователем

голоса
1

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

Ответил 22/10/2008 в 15:35
источник пользователем

голоса
1

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

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

Ответил 22/10/2008 в 15:26
источник пользователем

голоса
1

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

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

Кроме того, есть возможность просматривать список с для цикла.

Ответил 22/10/2008 в 15:25
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more