Есть ли какая-то редкая языковая конструкция, с которой я не сталкивался (например, немногие, которые я недавно выучил, некоторые по переполнению стека) в C # для получения значения, представляющего текущую итерацию цикла foreach?
Например, в настоящее время я делаю что-то вроде этого в зависимости от обстоятельств:
int i = 0;
foreach (Object o in collection)
{
// ...
i++;
}
foreach
состоит в том, чтобы предоставить общий итерационный механизм для всех коллекций независимо от того, являются ли они indexable (List
) или нет (Dictionary
).Dictionary
он и не индексируется, итерацияDictionary
выполняет его в определенном порядке (т. Е. Перечислитель индексируется благодаря тому, что он последовательно выдает элементы). В этом смысле мы могли бы сказать, что мы ищем не индекс в коллекции, а скорее индекс текущего перечисляемого элемента в перечислении (т.е. находимся ли мы в первом, пятом или последнем перечисляемом элементе).Ответы:
Это
foreach
для перебора коллекций, которые реализуютIEnumerable
. Это делается путем вызоваGetEnumerator
коллекции, которая вернетEnumerator
.Этот перечислитель имеет метод и свойство:
Current
возвращает объект, на котором в данный момент находится Enumerator,MoveNext
обновляетCurrent
следующий объект.Понятие индекса чуждо понятию перечисления и не может быть сделано.
По этой причине большинство коллекций можно просматривать с помощью индексатора и конструкции цикла for.
Я очень предпочитаю использовать цикл for в этой ситуации по сравнению с отслеживанием индекса с помощью локальной переменной.
источник
for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
zip
функции Python ).Ян Мерсер опубликовал подобное решение в блоге Фила Хаака :
Это получает вас item (
item.value
) и его index (item.i
), используя перегрузку LINQSelect
:new { i, value }
Создает новый анонимный объект .Распределения кучи можно избежать,
ValueTuple
если вы используете C # 7.0 или новее:Вы также можете устранить
item.
с помощью автоматической деструктуризации:источник
Наконец, C # 7 имеет приличный синтаксис для получения индекса внутри
foreach
цикла (т. Е. Кортежей):Небольшой метод расширения будет необходимо:
источник
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
Enumerated
чтобы он был более узнаваемым для людей, привыкших к другим языкам (и, возможно, поменяйте местами порядок параметров кортежа).WithIndex
Во всяком случае , это не очевидно.Может сделать что-то вроде этого:
источник
values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
Я не согласен с комментариями о том, что
for
цикл является лучшим выбором в большинстве случаев.foreach
является полезной конструкцией и не может быть замененаfor
циклом при любых обстоятельствах.Например, если у вас есть DataReader и вы перебираете все записи, используя его,
foreach
он автоматически вызывает метод Dispose и закрывает читатель (который затем может автоматически закрыть соединение). Поэтому это безопаснее, так как предотвращает утечки соединения, даже если вы забыли закрыть ридер.(Конечно, это хорошая практика - всегда закрывать читатели, но компилятор не поймает его, если вы этого не сделаете - вы не можете гарантировать, что закрыли все читатели, но вы можете сделать это с большей вероятностью, что не будете пропускать соединения, получая в привычке использовать foreach.)
Могут быть и другие примеры использования неявного вызова
Dispose
метода.источник
foreach
, чем отличаетсяfor
(и ближе кwhile
) на Programmers.SE .Литеральный ответ - предупреждение, производительность может быть не такой хорошей, как при использовании
int
для отслеживания индекса. По крайней мере, это лучше, чем использоватьIndexOf
.Вам просто нужно использовать перегрузку индексации Select, чтобы обернуть каждый элемент в коллекции анонимным объектом, который знает индекс. Это может быть сделано против всего, что реализует IEnumerable.
источник
Используя LINQ, C # 7 и
System.ValueTuple
пакет NuGet, вы можете сделать это:Вы можете использовать обычную
foreach
конструкцию и иметь возможность прямого доступа к значению и индексу, а не как к элементу объекта, и оба поля сохраняются только в области действия цикла. По этим причинам я считаю, что это лучшее решение, если вы можете использовать C # 7 иSystem.ValueTuple
.источник
Используя ответ @ FlySwat, я нашел следующее решение:
Вы получаете перечислитель, используя,
GetEnumerator
а затем цикл, используяfor
цикл. Однако хитрость заключается в том, чтобы сделать условие циклаlistEnumerator.MoveNext() == true
.Так как
MoveNext
метод перечислителя возвращает true, если есть следующий элемент, и к нему можно получить доступ, то условие цикла делает остановку цикла, когда у нас заканчиваются элементы для итерации.источник
IDisposable
.Нет ничего плохого в использовании переменной счетчика. Фактически, используете ли вы
for
,foreach
while
илиdo
переменная счетчика, должна где-то быть объявлена и увеличена.Так что используйте эту идиому, если вы не уверены, что у вас есть соответственно проиндексированная коллекция:
В противном случае используйте этот, если вы знаете, что ваша индексируемая коллекция O (1) для доступа к индексу (для чего она
Array
и, вероятно, будет использоватьсяList<T>
(в документации не сказано), но не обязательно для других типов (таких какLinkedList
)):Никогда не должно быть необходимости «вручную» управлять
IEnumerator
операцией, вызываяMoveNext()
и опрашиваяCurrent
-foreach
это спасает вас от этой конкретной проблемы ... если вам нужно пропустить элементы, просто используйте acontinue
в теле цикла.И просто для полноты, в зависимости от того, что вы делали со своим индексом (вышеупомянутые конструкции предлагают большую гибкость), вы можете использовать Parallel LINQ:
Мы используем
AsParallel()
выше, потому что это уже 2014, и мы хотим эффективно использовать эти несколько ядер, чтобы ускорить процесс. Кроме того, для «последовательного» LINQ вы используете толькоForEach()
метод расширенияList<T>
иArray
... и не ясно, что его использование лучше, чем простоеforeach
, поскольку вы все еще используете однопоточный для более уродливого синтаксиса.источник
Вы могли бы обернуть оригинальный перечислитель другим, который содержит информацию индекса.
Вот код для
ForEachHelper
класса.источник
Вот решение, которое я только что придумал для этой проблемы
Оригинальный код:
Обновленный код
Метод продления:
источник
Просто добавьте свой собственный индекс. Будь проще.
источник
Это будет работать только для List, а не для любого IEnumerable, но в LINQ есть это:
@ Джонатан Я не сказал, что это был отличный ответ, я просто сказал, что он просто показывает, что можно сделать то, что он просил :)
@Graphain Я бы не ожидал, что он будет быстрым - я не совсем уверен, как это работает, он может повторять весь список каждый раз, чтобы найти соответствующий объект, который был бы адским сравнением.
Тем не менее, List может хранить индекс каждого объекта вместе с подсчетом.
Джонатан, кажется, имеет лучшую идею, если он уточнит?
Было бы лучше просто посчитать, где вы находитесь в foreach, хотя, проще и более адаптируемо.
источник
C # 7, наконец, дает нам элегантный способ сделать это:
источник
Почему foreach ?!
Самый простой способ использования для вместо Еогеаспа , если вы используете список :
Или, если вы хотите использовать foreach:
Вы можете использовать это, чтобы узнать индекс каждого цикла:
источник
Это будет работать для поддержки коллекций
IList
.источник
O(n^2)
как в большинстве реализацийIndexOf
естьO(n)
. 2) Сбой, если в списке есть повторяющиеся элементы.Это то, как я это делаю, что приятно из-за своей простоты / краткости, но если вы много делаете в теле цикла
obj.Value
, оно довольно быстро устареет.источник
Ответ: лоббируйте языковую группу C # для прямой языковой поддержки.
Ведущий ответ гласит:
Хотя это верно для текущей версии языка C # (2020), это не концептуальное ограничение CLR / Language, это можно сделать.
Команда разработчиков языка Microsoft C # может создать новую функцию языка C #, добавив поддержку нового интерфейса IIndexedEnumerable
Если
foreach ()
используется иwith var index
присутствует, то компилятор ожидает, что коллекция элементов объявитIIndexedEnumerable
интерфейс. Если интерфейс отсутствует, компилятор может полифиллом обернуть исходный код объектом IndexedEnumerable, который добавляет код для отслеживания индекса.Позже, CLR может быть обновлен, чтобы иметь внутреннее отслеживание индекса, которое используется, только если
with
указано ключевое слово, а источник не реализует напрямуюIIndexedEnumerable
Почему:
В то время как большинство людей здесь не Microsoft сотрудников, это правильный ответ, вы можете лоббировать Microsoft , чтобы добавить такую функцию. Вы уже могли создать свой собственный итератор с функцией расширения и использовать кортежи , но Microsoft могла бы посыпать синтаксический сахар, чтобы избежать функции расширения
источник
Если коллекция представляет собой список, вы можете использовать List.IndexOf, например:
источник
Лучше использовать ключевое слово
continue
безопасную конструкцию, как этоисточник
Вы можете написать свой цикл так:
После добавления следующей структуры и метода расширения.
Метод struct и extension инкапсулирует функциональность Enumerable.Select.
источник
Мое решение этой проблемы - метод расширения
WithIndex()
,http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs
Используйте это как
источник
struct
для (index, item) пары.Для интереса Фил Хаак только что написал пример этого в контексте шаблонного делегата Razor ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )
По сути, он пишет метод расширения, который оборачивает итерацию в класс «IteratedItem» (см. Ниже), предоставляя доступ к индексу, а также к элементу во время итерации.
Однако, хотя это было бы хорошо в среде без Razor, если вы выполняете одну операцию (то есть ту, которая может быть предоставлена как лямбда), это не будет надежной заменой синтаксиса for / foreach в контекстах без Razor. ,
источник
Я не думаю, что это должно быть достаточно эффективно, но это работает:
источник
Я построил это в LINQPad :
Вы также можете просто использовать
string.join
:источник
n
элементы, то операции выполняютсяn * n
илиn
-квадратируются. en.wikipedia.org/wiki/…Я не верю, что есть способ получить значение текущей итерации цикла foreach. Считать себя, кажется, лучший способ.
Могу я спросить, почему вы хотели бы знать?
Похоже, вам больше всего нравится делать одну из трех вещей:
1) Получение объекта из коллекции, но в этом случае он у вас уже есть.
2) Подсчет объектов для последующей постобработки ... коллекции имеют свойство Count, которое вы можете использовать.
3) Установка свойства для объекта в зависимости от его порядка в цикле ... хотя вы могли бы легко установить это при добавлении объекта в коллекцию.
источник
Если ваша коллекция не может вернуть индекс объекта с помощью какого-либо метода, единственный способ - использовать счетчик, как в вашем примере.
Однако при работе с индексами единственным разумным ответом на проблему является использование цикла for. Все остальное вносит сложности в код, не говоря уже о сложности времени и пространства.
источник
Как насчет этого? Обратите внимание, что myDelimitedString может быть нулевым, если myEnumerable пуст.
источник
У меня просто была эта проблема, но обдумывание проблемы в моем случае дало лучшее решение, не связанное с ожидаемым решением.
Это может быть довольно распространенным случаем, в основном, я читаю из одного списка источников и создаю объекты на их основе в списке адресатов, однако мне нужно сначала проверить, допустимы ли исходные элементы, и хочу вернуть строку любого ошибка. На первый взгляд, я хочу получить индекс в перечислителе объекта в свойстве Current, однако, поскольку я копирую эти элементы, я все равно неявно знаю текущий индекс из текущего места назначения. Очевидно, это зависит от вашего целевого объекта, но для меня это был список, и, скорее всего, он будет реализовывать ICollection.
т.е.
Я думаю, что это не всегда применимо, но достаточно часто, чтобы упоминать.
Во всяком случае, дело в том, что иногда в вашей логике уже есть неочевидное решение ...
источник
Я не был уверен, что вы пытаетесь сделать с индексной информацией, основанной на вопросе. Однако в C # обычно можно адаптировать метод IEnumerable.Select, чтобы получить индекс из всего, что вы хотите. Например, я мог бы использовать что-то вроде этого для того, является ли значение нечетным или четным.
Это даст вам словарь по имени того, был ли элемент нечетным (1) или четным (0) в списке.
источник