Существует много способов перебора коллекции. Любопытно, есть ли какие-либо различия, или почему вы бы использовали один способ по сравнению с другим.
Первый тип:
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>
});
Я полагаю, что мне хотелось бы сказать, что вместо анонимного делегата, которого я использую выше, у вас будет делегат многократного использования, который вы можете указать ...
Ответы:
Существует одно важное и полезное различие между ними.
Поскольку .ForEach использует
for
цикл для итерации коллекции, это допустимо (edit: до .net 4.5 - реализация изменилась, и они оба бросают):тогда как
foreach
использует перечислитель, так что это недопустимо:tl; dr: НЕ копируйте этот код в свое приложение!
Эти примеры не являются лучшей практикой, они просто демонстрируют различия между
ForEach()
иforeach
.Удаление элементов из списка в
for
цикле может иметь побочные эффекты. Наиболее распространенный из них описан в комментариях к этому вопросу.Как правило, если вы хотите удалить несколько элементов из списка, вы должны отделить определение того, какие элементы удалить от фактического удаления. Это не делает ваш код компактным, но гарантирует, что вы не пропустите ни одного элемента.
источник
У нас был некоторый код здесь (в 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 и перестройку, а не изменяя свой код.
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 уже не делать то , что вы хотите от петли.источник
Ради интереса, я вставил List в отражатель, и в результате получился C #:
Точно так же MoveNext в Enumerator, который используется foreach, таков:
List.ForEach гораздо более урезан, чем MoveNext - гораздо меньше обработки - с большей вероятностью превратит JIT во что-то эффективное.
Кроме того, foreach () будет выделять новый перечислитель, несмотря ни на что. GC - ваш друг, но если вы неоднократно выполняете один и тот же foreach, это создаст больше одноразовых объектов, в отличие от повторного использования одного и того же делегата - НО - это действительно незначительный случай. При обычном использовании вы увидите мало или нет разницы.
источник
Я знаю две неясные вещи, которые отличают их. Иди ко мне!
Во-первых, есть классическая ошибка создания делегата для каждого элемента в списке. Если вы используете ключевое слово foreach, все ваши делегаты могут в конечном итоге ссылаться на последний элемент списка:
Метод List.ForEach не имеет этой проблемы. Текущий элемент итерации передается по значению в качестве аргумента во внешнюю лямбду, а затем внутренняя лямбда правильно захватывает этот аргумент в своем собственном закрытии. Задача решена.
(К сожалению, я считаю, что ForEach является членом List, а не методом расширения, хотя его легко определить самостоятельно, поэтому у вас есть эта возможность для любого перечислимого типа.)
Во-вторых, подход метода ForEach имеет ограничение. Если вы реализуете IEnumerable с использованием yield return, вы не сможете выполнить возврат дохода внутри лямбды. Таким образом, циклически просматривая элементы в коллекции для получения возвращаемых вещей, этот метод невозможен. Вам нужно будет использовать ключевое слово foreach и обойти проблему закрытия, вручную сделав копию текущего значения цикла внутри цикла.
Больше здесь
источник
foreach
«исправлена» в C # 5. stackoverflow.com/questions/8898925/…Я предполагаю, что
someList.ForEach()
вызов можно легко распараллелить, тогда как нормальныйforeach
не так легко запустить параллельно. Вы можете легко запустить несколько разных делегатов на разных ядрах, что не так просто сделать с обычнымforeach
.Просто мои 2 цента
источник
Как говорится, дьявол кроется в деталях ...
Самое большое различие между этими двумя методами перечисления состоит в том, что он
foreach
переносит состояние, аForEach(x => { })
не переносит .Но давайте копнем немного глубже, потому что есть некоторые вещи, о которых вы должны знать, которые могут повлиять на ваше решение, и есть некоторые предостережения, о которых вы должны знать при кодировании для любого случая.
Давайте использовать
List<T>
в нашем маленьком эксперименте, чтобы наблюдать за поведением. Для этого эксперимента я использую .NET 4.7.2:Давайте повторим это
foreach
сначала:Мы могли бы расширить это в:
С счетчиком в руке, заглянув под крышки, мы получим:
Две вещи становятся очевидными:
Это, конечно, никоим образом не безопасно. Как указывалось выше, изменение коллекции во время итерации - просто плохое моджо.
Но как насчет проблемы того, чтобы коллекция стала недействительной во время итерации с помощью средств, не связанных с нами во время итерации? В соответствии с рекомендациями рекомендуется создавать версии коллекции во время операций и итерации, а также проверять версии, чтобы определить, когда базовая коллекция изменяется.
Здесь вещи становятся действительно темными. Согласно документации Microsoft:
Ну, что это значит? Например, то, что
List<T>
реализация обработки исключений не означает, что все коллекции, которые реализуют,IList<T>
будут делать то же самое. Кажется, это явное нарушение принципа подстановки Лискова:Другая проблема заключается в том, что перечислитель должен реализовать
IDisposable
- это означает еще один источник потенциальных утечек памяти, не только если вызывающая сторона ошибается, но и если автор неправильно реализуетDispose
шаблон.Наконец, у нас есть проблема на всю жизнь ... что произойдет, если итератор допустим, но основная коллекция исчезла? Теперь мы сделаем снимок того, что было ... когда вы разделяете время жизни коллекции и ее итераторов, вы напрашиваетесь на неприятности.
Давайте теперь рассмотрим
ForEach(x => { })
:Это расширяется до:
Важным примечанием является следующее:
for (int index = 0; index < this._size && ... ; ++index) action(this._items[index]);
Этот код не выделяет никаких перечислителей (ничего
Dispose
) и не делает паузу во время итерации.Обратите внимание, что при этом также выполняется поверхностная копия базовой коллекции, но теперь коллекция является моментальным снимком во времени. Если автор неправильно реализует проверку на изменение или устаревание коллекции, снимок остается действительным.
Это никоим образом не защитит вас от проблемы жизненных проблем ... если основная коллекция исчезнет, теперь у вас есть мелкая копия, которая указывает на то, что было ... но, по крайней мере, у вас нет
Dispose
проблем с иметь дело с осиротевшими итераторами ...Да, я сказал итераторы ... иногда выгодно иметь состояние. Предположим, вы хотите сохранить что-то похожее на курсор в базе данных ... может быть, стоит использовать несколько
foreach
стилейIterator<T>
. Мне лично не нравится этот стиль дизайна, так как существует слишком много проблем на всю жизнь, и вы полагаетесь на милость авторов сборников, на которые вы полагаетесь (если вы буквально не пишете все сами с нуля).Всегда есть третий вариант ...
Это не сексуально, но у него есть зубы (извинения Тому Крузу и фильму The Firm )
Это ваш выбор, но теперь вы знаете, и это может быть осознанным.
источник
Вы можете назвать анонимного делегата :-)
И вы можете написать второй как:
Который я предпочитаю, и экономит много печатать.
Как говорит Иоахим, параллелизм легче применить ко второй форме.
источник
За кулисами анонимный делегат превращается в реальный метод, так что вы можете получить некоторые накладные расходы при втором выборе, если компилятор не решил встроить функцию. Кроме того, любые локальные переменные, на которые ссылается тело примера анонимного делегата, будут меняться по своей природе из-за хитрости компилятора, чтобы скрыть тот факт, что он компилируется в новый метод. Больше информации здесь о том, как C # делает это волшебство:
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
источник
Функция ForEach является членом универсального класса List.
Я создал следующее расширение для воспроизведения внутреннего кода:
Итак, в конце мы используем обычный foreach (или цикл, если хотите).
С другой стороны, использование функции делегата - это просто еще один способ определить функцию, этот код:
эквивалентно:
или используя выражения labda:
источник
Вся область действия ForEach (функция делегата) обрабатывается как одна строка кода (вызывая функцию), и вы не можете устанавливать точки останова или входить в код. Если происходит необработанное исключение, помечается весь блок.
источник
List.ForEach () считается более функциональным.
List.ForEach()
говорит, что вы хотите сделать.foreach(item in list)
также говорит, как именно вы хотите это сделать. Это оставляетList.ForEach
свободно изменять реализацию том , как части в будущем. Например, гипотетическая будущая версия .Net может всегда работатьList.ForEach
параллельно, при условии, что на данный момент у каждого есть несколько ядер ЦП, которые обычно простаивают.С другой стороны,
foreach (item in list)
дает вам немного больше контроля над циклом. Например, вы знаете, что элементы будут повторяться в некотором последовательном порядке, и вы могли бы легко сломаться в середине, если элемент удовлетворяет некоторому условию.Некоторые более свежие замечания по этому вопросу доступны здесь:
источник
Второй показанный способ использует метод расширения для выполнения метода делегата для каждого элемента в списке.
Таким образом, у вас есть другой вызов делегата (= метода).
Кроме того, существует возможность повторения списка с циклом for .
источник
Остерегайтесь того, как выйти из универсального метода .ForEach - см. Это обсуждение . Хотя ссылка, кажется, говорит, что этот путь самый быстрый. Не уверен почему - вы могли бы подумать, что они будут эквивалентны после компиляции ...
источник
Есть способ, я сделал это в своем приложении:
Вы можете использовать в качестве элемента из foreach
источник