Есть ли какие-то редкие языковые конструкции, с которыми я не сталкивался (например, те, что я узнал недавно, некоторые из них о переполнении стека) в C#, чтобы получить значение, представляющее текущую итерацию цикла foreach?
Например, сейчас я делаю что-то подобное в зависимости от обстоятельств:
int i = 0;
foreach (Object o in collection)
{
// ...
i++;
}
Я бы сказал, что основная цель foreach - предоставить общий механизм итерации для всех коллекций, независимо от того, являются ли они индексируемыми (List) или нет (Dictionary).
Привет, Брайан Гидеон - однозначно согласен (это было несколько лет назад, и в то время я был гораздо менее опытен). Однако, хотя Dictionary не индексируется, итерация Dictionary действительно проходит по нему в определенном порядке (то есть Enumerator индексируется тем фактом, что он выдает элементы последовательно). В этом смысле мы могли бы сказать, что мы ищем не индекс в коллекции, а скорее индекс текущего перечисляемого элемента в перечислении (т.е. находимся ли мы в первом, пятом или последнем перечисляемом элементе).
foreach также позволяет компилятору пропускать границы, проверяя каждый доступ к массиву в скомпилированном коде. Использование for с индексом заставит среду выполнения проверить, безопасен ли ваш доступ к индексу.
Но это неправда. Если вы не измените переменную итерации цикла for внутри цикла, компилятор знает, каковы его границы, и ему не нужно проверять их снова. Это настолько распространенный случай, что любой достойный компилятор реализует его.
Усложняет ли foreach индикатор выполнения для длительных операций? (потому что индекса нет)
foreach - это конструкция для потребляющих последовательностей (типы IEnumerable). Последовательность не обязательно является коллекцией и не обязательно находится в памяти. Гарантируется только последовательность. Вы не можете использовать конструкцию for со всеми последовательностями. Я бы сказал, что самый чистый способ сделать это - это то, как вы это уже делаете.
Хорошо. Не хватает читабельности? Я так не думаю. Слишком много писали? Стоимость набора пренебрежимо мала, если фраза не используется очень и очень часто. Оптимизация на низком уровне? Не делайте этого на C#. Только не надо. Какая-то элегантность? Что ж, в этом случае есть синтаксис WithIndex, который хорош, но то, что у вас есть, годится. Но не используйте LINQ только из соображений элегантности. Для меня это запах кода. Я использую LINQ только тогда, когда могу добиться значительного улучшения читаемости и / или скорости кодирования. Использование LINQ для простого цикла недопустимо.





Если ваша коллекция не может вернуть индекс объекта с помощью какого-либо метода, единственный способ - использовать счетчик, как в вашем примере.
Однако при работе с индексами единственный разумный ответ на проблему - использовать цикл for. Все остальное усложняет код, не говоря уже о пространственной и временной сложности.
Я не верю, что есть способ получить значение текущей итерации цикла foreach. Считать себя, кажется, лучший способ.
Могу я спросить, почему вы хотите знать?
Кажется, что вы, скорее всего, будете делать одно из трех:
1) Получение объекта из коллекции, но в данном случае он у вас уже есть.
2) Подсчет объектов для последующей постобработки ... коллекции имеют свойство Count, которое вы могли бы использовать.
3) Установка свойства объекта на основе его порядка в цикле ... хотя вы можете легко установить это, когда добавляете объект в коллекцию.
4) Случай, с которым я столкнулся несколько раз, - это что-то другое, что нужно сделать на первом или последнем проходе - скажем, список объектов, которые вы собираетесь распечатать, и вам нужны запятые между элементами, но не после последнего элемента.
foreach предназначен для перебора коллекций, реализующих IEnumerable. Он делает это, вызывая GetEnumerator в коллекции, которая возвращает Enumerator.
У этого перечислителя есть метод и свойство:
MoveNext()CurrentCurrent возвращает объект, на котором в настоящее время находится Enumerator, MoveNext обновляет Current до следующего объекта.
Понятие индекса чуждо понятию перечисления и не может быть выполнено.
По этой причине большинство коллекций можно просматривать с помощью индексатора и конструкции цикла for.
В этой ситуации я предпочитаю использовать цикл for по сравнению с отслеживанием индекса с помощью локальной переменной.
«Очевидно, что понятие индекса чуждо концепции перечисления и не может быть выполнено». - Это чушь, как ясно показывают ответы Дэвида Б. и Бкахилла. Индекс - это перечисление по диапазону, и нет причин, по которым нельзя перечислять две вещи параллельно ... это именно то, что делает форма индексации Enumerable.Select.
Пример базового кода: for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
@JimBalter: связанный список - это пример IEnumerable, у которого нет индекса. Ответ FlySwat правильный, хотя, возможно, вы не согласились с его словом «очевидно», и в этом случае я согласен; Для многих это не так очевидно.
@Pretzel Заявление, которое я процитировал, (очевидно) неверно, и я объяснил, почему. Тот факт, что у связанного списка «нет индекса», совершенно неуместен и демонстрирует чрезвычайную путаницу.
@JimBalter: Я не собираюсь с тобой спорить. Может быть, Microsoft следовало назвать его I-Iterable вместо IEnumerable (что, я признаю, подразумевает число). Но реализация такова, как есть. Индекса нет. (Я предполагаю, что из соображений безопасности потоков ...)
P.S. Просто прочтите msdn.microsoft.com/en-us/library/bb534869%28v=vs.100%29.aspx, о котором я уже упоминал несколько раз. Обратите внимание на «вы можете вызвать этот метод как метод экземпляра на любой объект типа IEnumerable <TSource>».
Суть в том, что «Связанный список - это пример IEnumerable, у которого нет индекса» - это пустяк, который совершенно неуместен. Никто никогда не утверждал, что все IEnumerables «имеют индекс».
Крендель: Ваше использование Джона Скита в качестве образцового гражданина ошибочно, поскольку он запугает (и проголосует против) того, кто не согласен с ним, как я испытал. Джим Балтер: Некоторые из ваших комментариев были чрезмерно агрессивными, и вы могли бы выразить свою точку зрения с меньшим гневом и большей образованностью.
Любопытно, правильно ли упрощенное утверждение «понятие индекса чуждо понятию перечисления»?
@Pretzel Jim считает, что если вы можете сопоставить элементы с последовательностью целых чисел, вы можете ее индексировать. То, что сам класс не хранит индекс, не имеет значения. Кроме того, наличие ордера в связанном списке делает только усиливает позицию Джима. Все, что вам нужно сделать, это пронумеровать каждый элемент по порядку. В частности, вы можете добиться этого, увеличивая счетчик пока, который вы повторяете, или вы можете создать список целых чисел той же длины, а затем заархивировать их (как в функции Python zip).
Это жалкий ответ. Можете ли вы представить, было ли это размещено на вопросе Python или Rust? Люди будут смеяться над этим, когда публикуют указатель на функцию enumerate.
Можем ли мы заменить этот «ответ» ответом @bcahill, поскольку он отвечает на заданный вопрос и набирает больше голосов, чем этот в настоящее время (489 против 520)?
Enumerable буквально означает «можно пронумеровать». Другое дело, что API или базовая структура данных не предоставляет средств для произвольного доступа.
Как Microsoft из вас, Microsoft
Чтобы быть ясным, вопрос требует индекса «текущей итерации цикла foreach», который не совпадает с естественным индексом перечисляемого (если он вообще есть). Если ваш перечислимый представляет, например, пункты 5, 6, 7 в каком-то списке, значит, вы правы, нет никакого способа получить это. Но OP на самом деле хотел 0, 1, 2 в этом случае, что, конечно, возможно. По сути, это правильный ответ на другой вопрос.
Мог бы сделать что-то вроде этого:
public static class ForEachExtensions
{
public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> handler)
{
int idx = 0;
foreach (T item in enumerable)
handler(item, idx++);
}
}
public class Example
{
public static void Main()
{
string[] values = new[] { "foo", "bar", "baz" };
values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item));
}
}
Это «не совсем» решает проблему. Идея хороша, но не позволяет избежать дополнительной счетной переменной.
Это не работает, если у нас есть оператор return в нашем цикле for, если вы измените для него "ForEachWithIndex", тогда он не является универсальным, лучше написать обычный цикл for.
Ваш вызов ForEachWithIndex эквивалентен этому вызову с использованием Linq Select, который принимает строку и индекс: values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
Он будет работать только для списка, а не для любого IEnumerable, но в LINQ есть это:
IList<Object> collection = new List<Object> {
new Object(),
new Object(),
new Object(),
};
foreach (Object o in collection)
{
Console.WriteLine(collection.IndexOf(o));
}
Console.ReadLine();
@ Джонатан Я не сказал, что это отличный ответ, я просто сказал, что он просто показывает, что можно сделать то, что он просил :)
@Graphain Я бы не ожидал, что это будет быстро - я не совсем уверен, как это работает, он мог бы повторять через весь список каждый раз, чтобы найти подходящий объект, что было бы чертовски несравненно.
Тем не менее, List может хранить индекс каждого объекта вместе со счетчиком.
У Джонатана, кажется, есть идея получше, если он уточнит?
Было бы лучше просто подсчитать, где вы находитесь, в foreach, хотя это проще и адаптируется.
Не уверен в сильном голосовании против. Конечно, производительность делает это недопустимым, но вы ответили на вопрос!
Другая проблема заключается в том, что это работает, только если элементы в списке уникальны.
Буквальный ответ - предупреждение, производительность может быть не такой хорошей, как при использовании int для отслеживания индекса. По крайней мере, это лучше, чем использование IndexOf.
Вам просто нужно использовать перегрузку индексации Select, чтобы обернуть каждый элемент в коллекции анонимным объектом, который знает индекс. Это можно сделать против всего, что реализует IEnumerable.
System.Collections.IEnumerable collection = Enumerable.Range(100, 10);
foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i}))
{
Console.WriteLine("{0} {1}", o.i, o.x);
}
Единственная причина использовать OfType <T> () вместо Cast <T> () заключается в том, что некоторые элементы в перечислении могут не выполнить явное приведение. На самом деле, этого никогда не будет.
Конечно, за исключением другой причины использовать OfType вместо Cast - я никогда не использую Cast.
Зачем вообще был нужен OfType () (или Cast, если желательно)? Select можно назвать прямо по коллекции, не так ли?
@UuDdLrLrSs Нет. Для выбора требуется параметр типа. неуниверсальный IEnumerable не имеет параметра типа для предоставления.
@AmyB, спасибо! Я упустил из виду, что эта коллекция была только IEnumerable.
Я не согласен с комментариями о том, что в большинстве случаев шлейф for является лучшим выбором.
foreach - полезная конструкция, которую нельзя заменить петлей for при любых обстоятельствах.
Например, если у вас есть DataReader и циклически перебирают все записи с помощью foreach, он автоматически вызывает метод Утилизировать и закрывает считыватель (который затем может автоматически закрыть соединение). Таким образом, это безопаснее, поскольку предотвращает утечку соединения, даже если вы забыли закрыть считыватель.
(Конечно, это хорошая практика - всегда закрывать читатели, но компилятор не поймает это, если вы этого не сделаете - вы не можете гарантировать, что закрыли все читатели, но вы можете сделать это с большей вероятностью, что не потеряете соединения, получив в привычке использовать foreach.)
Могут быть и другие полезные примеры неявного вызова метода Dispose.
Спасибо за указание на это. Довольно тонкий. Вы можете получить дополнительную информацию на pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerat или и msdn.microsoft.com/en-us/library/aa664754(VS.71).aspx.
+1. Более подробно о том, чем foreach отличается от for (и ближе к while), я писал на Программисты.SE.
Вот решение, которое я только что придумал для этой проблемы
Исходный код:
int index=0;
foreach (var item in enumerable)
{
blah(item, index); // some code that depends on the index
index++;
}
Обновленный код
enumerable.ForEach((item, index) => blah(item, index));
Метод расширения:
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
{
var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
enumerable.Select((item, i) =>
{
action(item, i);
return unit;
}).ToList();
return pSource;
}
int index;
foreach (Object o in collection)
{
index = collection.indexOf(o);
}
Это будет работать для коллекций, поддерживающих IList.
Две проблемы: 1) Это O(n^2), поскольку в большинстве реализаций IndexOf - это O(n). 2) Это не удается, если в списке есть повторяющиеся элементы.
Примечание: O (n ^ 2) означает, что это может быть ужасно медленным для большой коллекции.
Отличное изобретение для использования метода IndexOf! Это то, что я искал, чтобы получить индекс (число) в цикле foreach! Большое спасибо
Боже, надеюсь, ты этим не воспользовался! :( Он ИСПОЛЬЗУЕТ ту переменную, которую вы не хотели создавать - на самом деле, она создаст n + 1 int, потому что эта функция тоже должна создать одну, чтобы вернуться, - и этот поиск indexof намного медленнее, чем одна операция целочисленного увеличения на каждом шаге. Почему люди не проголосуют за этот ответ?
Не используйте этот ответ, я нашел твердую правду, упомянутую в одном из комментариев. «Это не удается, если в списке есть повторяющиеся элементы» !!!
полезно во время сеанса отладки
Вы можете обернуть исходный перечислитель другим, который действительно содержит информацию об индексе.
foreach (var item in ForEachHelper.WithIndex(collection))
{
Console.Write("Index = " + item.Index);
Console.Write(";Value= " + item.Value);
Console.Write(";IsLast = " + item.IsLast);
Console.WriteLine();
}
Вот код класса ForEachHelper.
public static class ForEachHelper
{
public sealed class Item<T>
{
public int Index { get; set; }
public T Value { get; set; }
public bool IsLast { get; set; }
}
public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
{
Item<T> item = null;
foreach (T value in enumerable)
{
Item<T> next = new Item<T>();
next.Index = 0;
next.Value = value;
next.IsLast = false;
if (item != null)
{
next.Index = item.Index + 1;
yield return item;
}
item = next;
}
if (item != null)
{
item.IsLast = true;
yield return item;
}
}
}
На самом деле это не вернет индекс элемента. Вместо этого он вернет индекс внутри нумерованного списка, который может быть только подсписком списка, тем самым предоставляя вам точные данные только тогда, когда подсписок и список имеют равный размер. По сути, каждый раз, когда в коллекции есть объекты, не принадлежащие к запрошенному типу, ваш индекс будет неверным.
@Lucas: Нет, но он вернет индекс текущей итерации foreach. Вот в чем был вопрос.
Лучше использовать ключевое слово continue для безопасной конструкции, подобной этой
int i=-1;
foreach (Object o in collection)
{
++i;
//...
continue; //<--- safe to call, index will be increased
//...
}
Вот как я это делаю, что приятно своей простотой / краткостью, но если вы много делаете в теле цикла obj.Value, он довольно быстро устареет.
foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
...
}
Мое решение этой проблемы - метод расширения WithIndex(),
Используйте это как
var list = new List<int> { 1, 2, 3, 4, 5, 6 };
var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);
CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));
CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));
Я бы использовал struct для пары (index, item).
Как насчет этого? Обратите внимание, что myDelimitedString может иметь значение NULL, если myEnumerable пуст.
IEnumerator enumerator = myEnumerable.GetEnumerator();
string myDelimitedString;
string current = null;
if ( enumerator.MoveNext() )
current = (string)enumerator.Current;
while( null != current)
{
current = (string)enumerator.Current; }
myDelimitedString += current;
if ( enumerator.MoveNext() )
myDelimitedString += DELIMITER;
else
break;
}
Здесь множество проблем. А) дополнительная скоба. B) строка concat + = на итерацию цикла.
У меня просто была эта проблема, но обдумывание проблемы в моем случае дало лучшее решение, не связанное с ожидаемым решением.
Это может быть довольно распространенный случай, в основном, я читаю из одного исходного списка и создаю объекты на их основе в целевом списке, однако я должен сначала проверить, действительны ли исходные элементы, и хочу вернуть строку любого ошибка. На первый взгляд, я хочу получить индекс в перечислителе объекта в свойстве Current, однако, копируя эти элементы, я в любом случае неявно знаю текущий индекс из текущего места назначения. Очевидно, это зависит от вашего целевого объекта, но для меня это был список, и, скорее всего, он будет реализовывать ICollection.
т.е.
var destinationList = new List<someObject>();
foreach (var item in itemList)
{
var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);
if (stringArray.Length != 2)
{
//use the destinationList Count property to give us the index into the stringArray list
throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
}
else
{
destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
}
}
Не всегда применимо, но, я думаю, достаточно часто, чтобы о нем стоило упомянуть.
Во всяком случае, дело в том, что иногда в вашей логике уже есть неочевидное решение ...
Для интереса Фил Хаак просто написал пример этого в контексте Razor Templated Delegate (http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx).
Фактически он пишет метод расширения, который помещает итерацию в класс «IteratedItem» (см. Ниже), предоставляя доступ к индексу, а также к элементу во время итерации.
public class IndexedItem<TModel> {
public IndexedItem(int index, TModel item) {
Index = index;
Item = item;
}
public int Index { get; private set; }
public TModel Item { get; private set; }
}
Однако, хотя это было бы нормально в среде, отличной от Razor, если вы выполняете одну операцию (то есть ту, которая может быть предоставлена как лямбда), это не будет надежной заменой синтаксиса for / foreach в контекстах, отличных от Razor. .
Я не был уверен, что вы пытались сделать с информацией индекса, основываясь на вопросе. Однако в C# вы обычно можете адаптировать метод IEnumerable.Select, чтобы получить индекс из того, что вы хотите. Например, я могу использовать что-то подобное, чтобы определить, четное или нечетное значение.
string[] names = { "one", "two", "three" };
var oddOrEvenByName = names
.Select((name, index) => new KeyValuePair<string, int>(name, index % 2))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Это даст вам словарь по имени, в котором указано, был ли элемент нечетным (1) или четным (0) в списке.
Ян Мерсер опубликовал подобное решение на Блог Фила Хаака:
foreach (var item in Model.Select((value, i) => new { i, value }))
{
var value = item.value;
var index = item.i;
}
Это дает вам элемент (item.value) и его индекс (item.i) с помощью эта перегрузка LINQ Select:
the second parameter of the function [inside Select] represents the index of the source element.
new { i, value } создает новый анонимный объект.
Выделения кучи можно избежать с помощью ValueTuple, если вы используете C# 7.0 или новее:
foreach (var item in Model.Select((value, i) => ( value, i )))
{
var value = item.value;
var index = item.i;
}
Вы также можете удалить item., используя автоматическое деструктурирование:
<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
<li id = "item_@i">@value</li>
}
</ol>
Это решение подходит для случая шаблона Razor, где аккуратность шаблона является нетривиальной задачей дизайна, и вы также хотите использовать индекс каждого элемента, который перечисляется. Однако имейте в виду, что выделение объекта из «упаковки» увеличивает расходы (в пространстве и времени) в дополнение к (неизбежному) приращению целого числа.
чистая классность, но где это задокументировано .... Я имею в виду два параметра в делегате Select, я никогда этого не видел ... и я не могу найти объяснение в документации ... экспериментируя с этим выражением, я увидел это значение ничего не значит, имеет значение только позиция, первый параметр - элемент, второй параметр - индекс.
@mjsr Документация - здесь.
Извините, это умно, но действительно ли это более читаемо, чем создание индекса вне foreach и его увеличение в каждом цикле?
В более поздних версиях C# вы также используете кортежи, поэтому у вас будет что-то вроде этого: foreach (var (item, i) в Model.Select ((v, i) => (v, i))) Позволяет вам доступ к элементу и индексу (i) непосредственно внутри цикла for с деконструкцией кортежа.
Может ли кто-нибудь объяснить мне, почему это хороший ответ (на момент написания более 450 голосов)? Насколько я понимаю, это сложнее для понимания, чем простое увеличение счетчика, следовательно, оно менее удобно в обслуживании, использует больше памяти и, вероятно, работает медленнее. Я что-то пропустил?
@RichN Метод Linq .Select( T item, Int32 index ) теперь хорошо известен большинству программистов на C#. Это сбивает с толку, только если вы еще не знакомы с Linq и его функциональным стилем программирования.
Кто-нибудь делал какие-либо тесты производительности и использования памяти с версией tuple по сравнению с увеличивающимся счетчиком? Я уверен, что другие хотели бы знать, были ли производительность и память фактором в их требованиях к кодированию.
@RichN B / c Шлейфы for и foreach сейчас не "крутые" (/ s). Цепочка есть. Проблема в том, что LINQ не имеет forEachнарочно (как Эрик Липперт прекрасно объясняет). Это b / c foreach используется исключительно для побочных эффектов, нарушая метафору цепочки. LINQ называет это анти-шаблоном [я думаю, Эрик сказал бы, что он должен быть анти-шаблоном и на других языках]. Итак, вы видите, что foreach застрял в «прекратить использование циклов for; начать связывание« крутизна против », в C# [LINQ] нарочно нет цепочки foreach» crossfire
Я не думаю, что это должно быть достаточно эффективным, но это работает:
@foreach (var banner in Model.MainBanners) {
@Model.MainBanners.IndexOf(banner)
}
Вот еще одно решение этой проблемы с акцентом на поддержание синтаксиса как можно ближе к стандарту foreach.
Такая конструкция полезна, если вы хотите, чтобы ваши представления выглядели красивыми и чистыми в MVC. Например, вместо того, чтобы писать это обычным способом (который сложно красиво отформатировать):
<%int i=0;
foreach (var review in Model.ReviewsList) { %>
<div id = "review_<%=i%>">
<h3><%:review.Title%></h3>
</div>
<%i++;
} %>
Вместо этого вы могли бы написать это:
<%foreach (var review in Model.ReviewsList.WithIndex()) { %>
<div id = "review_<%=LoopHelper.Index()%>">
<h3><%:review.Title%></h3>
</div>
<%} %>
Я написал несколько вспомогательных методов, чтобы сделать это возможным:
public static class LoopHelper {
public static int Index() {
return (int)HttpContext.Current.Items["LoopHelper_Index"];
}
}
public static class LoopHelperExtensions {
public static IEnumerable<T> WithIndex<T>(this IEnumerable<T> that) {
return new EnumerableWithIndex<T>(that);
}
public class EnumerableWithIndex<T> : IEnumerable<T> {
public IEnumerable<T> Enumerable;
public EnumerableWithIndex(IEnumerable<T> enumerable) {
Enumerable = enumerable;
}
public IEnumerator<T> GetEnumerator() {
for (int i = 0; i < Enumerable.Count(); i++) {
HttpContext.Current.Items["LoopHelper_Index"] = i;
yield return Enumerable.ElementAt(i);
}
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
В не-веб-среде вы можете использовать static вместо HttpContext.Current.Items.
По сути, это глобальная переменная, поэтому у вас не может быть более одного вложенного цикла WithIndex, но в данном случае это не является серьезной проблемой.
Используя ответ @ FlySwat, я придумал следующее решение:
//var list = new List<int> { 1, 2, 3, 4, 5, 6 }; // Your sample collection
var listEnumerator = list.GetEnumerator(); // Get enumerator
for (var i = 0; listEnumerator.MoveNext() == true; i++)
{
int currentItem = listEnumerator.Current; // Get current item.
//Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and currentItem
}
Вы получаете перечислитель с помощью GetEnumerator, а затем выполняете цикл с помощью цикла for. Однако хитрость заключается в том, чтобы сделать условие цикла listEnumerator.MoveNext() == true.
Поскольку метод MoveNext перечислителя возвращает истину, если есть следующий элемент и к нему можно получить доступ, выполнение условия цикла останавливает цикл, когда у нас заканчиваются элементы для перебора.
Нет необходимости сравнивать listEnumerator.MoveNext () == true. Это все равно что спросить компьютер, правда ли == правда? :) Просто скажите, если listEnumerator.MoveNext () {}
@Zesty, ты абсолютно прав. Я чувствовал, что в этом случае удобнее добавлять его, особенно для людей, которые не привыкли вводить что-либо, кроме i <blahSize в качестве условия.
@Gezim Я не возражаю против предикатов, но я понимаю, что это не похоже на предикат.
Вы должны избавиться от счетчика.
@ AntonínLejsek Перечислитель не реализует IDisposable.
@EdwardBrey, вы правы, это хороший аргумент. Но если говорить о listEnumerator, то это общий перечислитель, поэтому он реализует IDisposable и должен быть удален.
@ AntonínLejsek Хороший улов для счетчика List<T>. Он реализует System.Collections.Generic.IEnumerator<T>, который наследует IDisposable. Перечислитель для List<T> ничего не делает в Dispose и не имеет финализатора, поэтому вызов Dispose в этом случае не имеет никакого эффекта, но может для других перечислимых.
Я построил это в LINQPad:
var listOfNames = new List<string>(){"John","Steve","Anna","Chris"};
var listCount = listOfNames.Count;
var NamesWithCommas = string.Empty;
foreach (var element in listOfNames)
{
NamesWithCommas += element;
if (listOfNames.IndexOf(element) != listCount -1)
{
NamesWithCommas += ", ";
}
}
NamesWithCommas.Dump(); //LINQPad method to write to console.
Вы также можете просто использовать string.join:
var joinResult = string.Join(",", listOfNames);
Вы также можете использовать string.join в C#. Например: var joinResult = string.Join (",", listOfNames); '
Может кто-нибудь объяснить мне, что это за штука O (n * n)?
@Axel в основном означает, что операции, необходимые для вычисления результата, увеличиваются квадратично, т.е. если есть элементы n, то операции выполняются n * n или n в квадрате. en.wikipedia.org/wiki/…
Нет ничего плохого в использовании переменной-счетчика. Фактически, независимо от того, используете ли вы for, foreach, while или do, переменная счетчика должна быть где-то объявлена и увеличена.
Поэтому используйте эту идиому, если вы не уверены, что у вас есть соответствующим образом проиндексированная коллекция:
var i = 0;
foreach (var e in collection) {
// Do stuff with 'e' and 'i'
i++;
}
В противном случае используйте это, если вы знать, что ваша коллекция индексируемый равна O (1) для доступа к индексу (это будет для Array и, вероятно, для List<T> (в документации не сказано), но не обязательно для других типов (например, LinkedList) ):
// Hope the JIT compiler optimises read of the 'Count' property!
for (var i = 0; i < collection.Count; i++) {
var e = collection[i];
// Do stuff with 'e' and 'i'
}
Никогда не должно быть необходимости «вручную» управлять IEnumerator, вызывая MoveNext() и запрашивая Current - foreach избавляет вас от этой конкретной проблемы ... если вам нужно пропустить элементы, просто используйте continue в теле цикла.
И просто для полноты, в зависимости от того, какой у вас был делает с вашим индексом (приведенные выше конструкции предлагают большую гибкость), вы можете использовать Parallel LINQ:
// First, filter 'e' based on 'i',
// then apply an action to remaining 'e'
collection
.AsParallel()
.Where((e,i) => /* filter with e,i */)
.ForAll(e => { /* use e, but don't modify it */ });
// Using 'e' and 'i', produce a new collection,
// where each element incorporates 'i'
collection
.AsParallel()
.Select((e, i) => new MyWrapper(e, i));
Мы используем AsParallel() выше, потому что уже 2014 год, и мы хотим эффективно использовать эти несколько ядер, чтобы ускорить процесс. Кроме того, для «последовательного» LINQ, вы получаете только метод расширения ForEach() на List<T> и Array ... и неясно, что его использование лучше, чем использование простого foreach, поскольку вы по-прежнему используете однопоточный режим для более уродливого синтаксиса.
Если коллекция представляет собой список, вы можете использовать List.IndexOf, как в:
foreach (Object o in collection)
{
// ...
@collection.IndexOf(o)
}
А теперь алгоритм O (n ^ 2) (если не хуже). Я бы подумал о очень, прежде чем использовать это. Это также дубликат ответа @crucible
@BradleyDotNET совершенно прав, не используйте эту версию.
Будьте осторожны с этим! Если у вас есть дублирующийся элемент в вашем списке, он получит позицию первого!
Это не отвечает на ваш конкретный вопрос, но ДЕЙСТВИТЕЛЬНО предлагает решение вашей проблемы: используйте цикл for для прохождения коллекции объектов. тогда у вас будет текущий индекс, над которым вы работаете.
// Untested
for (int i = 0; i < collection.Count; i++)
{
Console.WriteLine("My index is " + i);
}
Почему foreach ?!
Самый простой способ - использовать за вместо foreach если вы используете список:
for (int i = 0 ; i < myList.Count ; i++)
{
// Do something...
}
Или, если вы хотите использовать foreach:
foreach (string m in myList)
{
// Do something...
}
Вы можете использовать это, чтобы узнать индекс каждого цикла:
myList.indexOf(m)
Решение indexOf недопустимо для списка с дубликатами и также очень медленное.
Проблема, которую следует избегать, заключается в том, когда вы проходите IEnumerable несколько раз, например чтобы получить количество элементов, а затем каждый элемент. Это имеет значение, например, когда IEnumerable является результатом запроса к базе данных.
myList.IndexOf () равен O (n), поэтому ваш цикл будет O (n ^ 2).
Вы можете написать свой цикл так:
var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}
После добавления следующей структуры и метода расширения.
Структура и метод расширения инкапсулируют функциональность Enumerable.Select.
public struct ValueWithIndex<T>
{
public readonly T Value;
public readonly int Index;
public ValueWithIndex(T value, int index)
{
this.Value = value;
this.Index = index;
}
public static ValueWithIndex<T> Create(T value, int index)
{
return new ValueWithIndex<T>(value, index);
}
}
public static class ExtensionMethods
{
public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
{
return enumerable.Select(ValueWithIndex<T>.Create);
}
}
Наконец, C# 7 имеет приличный синтаксис для получения индекса внутри цикла foreach (то есть кортежей):
foreach (var (item, index) in collection.WithIndex())
{
Debug.WriteLine($"{index}: {item}");
}
Потребуется небольшой метод расширения:
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)
=> self.Select((item, index) => (item, index));
Этот ответ недооценен, кортежи намного чище.
Изменено для обработки нулевых коллекций: 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 в любом случае неочевиден.
@ 2Toad Я думаю, что для нулевого условия вы также можете использовать Enumerable.Empty<(T, int)>() как более эффективный, чем создание пустого списка.
Это должно быть решением сейчас
Эй, как это можно реализовать, понятия не имею: D
@JuanSebastianMontoyaContrer вы можете уточнить?
Этот ответ: лоббируйте языковую команду C# для прямой языковой поддержки.
Главный ответ гласит:
Obviously, the concept of an index is foreign to the concept of enumeration, and cannot be done.
Хотя это верно для текущей версии языка C# (2020), это не концептуальное ограничение CLR / языка, это можно сделать.
Группа разработчиков языка Microsoft C# могла бы создать новую функцию языка C#, добавив поддержку нового интерфейса IIndexedEnumerable.
foreach (var item in collection with var index)
{
Console.WriteLine("Iteration {0} has value {1}", index, item);
}
//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
Console.WriteLine("Iteration {0} has value {1}", index, item);
}
Если используется foreach () и присутствует with var index, то компилятор ожидает, что коллекция элементов объявит интерфейс IIndexedEnumerable. Если интерфейс отсутствует, компилятор может полифиллом обернуть источник объектом IndexedEnumerable, который добавляет в код для отслеживания индекса.
interface IIndexedEnumerable<T> : IEnumerable<T>
{
//Not index, because sometimes source IEnumerables are transient
public long IterationNumber { get; }
}
Позже CLR может быть обновлен, чтобы иметь внутреннее отслеживание индекса, которое используется только в том случае, если указано ключевое слово with, а источник не реализует IIndexedEnumerable напрямую.
Почему:
Хотя большинство людей здесь не являются сотрудниками Microsoft, это правильный ответ а, вы можете лоббировать Microsoft, чтобы добавить такую функцию. Вы уже можете создать свой собственный итератор с функция расширения и использование кортежей, но Microsoft может добавить синтаксический сахар, чтобы избежать функции расширения.
Подождите, так эта языковая функция уже существует, или она предлагается в будущем?
@Pavel Я обновил ответ, чтобы было понятно. Этот ответ был дан, чтобы опровергнуть основной ответ, который гласит: «Очевидно, что понятие индекса чуждо концепции перечисления и не может быть выполнено».
Наконец, C# 7 дает нам элегантный способ сделать это:
static class Extensions
{
public static IEnumerable<(int, T)> Enumerate<T>(
this IEnumerable<T> input,
int start = 0
)
{
int i = start;
foreach (var t in input)
{
yield return (i++, t);
}
}
}
class Program
{
static void Main(string[] args)
{
var s = new string[]
{
"Alpha",
"Bravo",
"Charlie",
"Delta"
};
foreach (var (i, t) in s.Enumerate())
{
Console.WriteLine($"{i}: {t}");
}
}
}
Да, и MS должна расширить CLR / BCL, чтобы сделать такие вещи родными.
Используя LINQ, C# 7 и пакет NuGet System.ValueTuple, вы можете сделать это:
foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
Console.WriteLine(value + " is at index " + index);
}
Вы можете использовать обычную конструкцию foreach и иметь возможность напрямую обращаться к значению и индексу, а не как член объекта, и сохранять оба поля только в области действия цикла. По этим причинам я считаю, что это лучшее решение, если вы можете использовать C# 7 и System.ValueTuple.
Чем это отличается от user1414213562 ответ?
@EdwardBrey Полагаю, это не так. На этот вопрос много ответов, я, вероятно, просто пропустил его или не заметил, что он делал, потому что он разделил часть логики на метод расширения.
Это другое дело, потому что .Select встроен в LINQ. Вам не нужно писать собственную функцию? Однако вам нужно будет установить VS "System.ValueTuple".
Просто добавьте свой индекс. Будь проще.
int i = 0;
foreach (var item in Collection)
{
item.index = i;
++i;
}
хочу обсудить этот вопрос более теоретически (так как практических ответов на него уже достаточно)
.net имеет очень красивую модель абстракции для групп данных (также известных как коллекции).
IEnumerable, это просто группа данных, которую вы можете перечислить. Не имеет значения, КАК вы перечисляете, просто вы можете перечислить некоторые данные. И это перечисление выполняется совершенно другим объектом, IEnumerator.эти интерфейсы определяются следующим образом:
//
// Summary:
// Exposes an enumerator, which supports a simple iteration over a non-generic collection.
public interface IEnumerable
{
//
// Summary:
// Returns an enumerator that iterates through a collection.
//
// Returns:
// An System.Collections.IEnumerator object that can be used to iterate through
// the collection.
IEnumerator GetEnumerator();
}
//
// Summary:
// Supports a simple iteration over a non-generic collection.
public interface IEnumerator
{
//
// Summary:
// Gets the element in the collection at the current position of the enumerator.
//
// Returns:
// The element in the collection at the current position of the enumerator.
object Current { get; }
//
// Summary:
// Advances the enumerator to the next element of the collection.
//
// Returns:
// true if the enumerator was successfully advanced to the next element; false if
// the enumerator has passed the end of the collection.
//
// Exceptions:
// T:System.InvalidOperationException:
// The collection was modified after the enumerator was created.
bool MoveNext();
//
// Summary:
// Sets the enumerator to its initial position, which is before the first element
// in the collection.
//
// Exceptions:
// T:System.InvalidOperationException:
// The collection was modified after the enumerator was created.
void Reset();
}
как вы могли заметить, интерфейс IEnumerator не «знает», что такое индекс, он просто знает, на какой элемент он сейчас указывает и как перейти к следующему.
Теперь вот трюк: foreach считает каждую входную коллекцию IEnumerable, даже если это более конкретная реализация, такая как IList<T> (который наследуется от IEnumerable), он будет видеть только абстрактный интерфейс IEnumerable.
то, что на самом деле делает foreach, вызывает GetEnumerator в коллекции и вызывает MoveNext до тех пор, пока он не вернет false.
Итак, вот проблема, вы хотите определить конкретное понятие «Индексы» на абстрактном понятии «Перечисления», встроенная конструкция foreach не дает вам такой возможности, поэтому ваш единственный способ - определить его самостоятельно, либо с помощью вы делаете это изначально (создаете счетчик вручную) или просто используете реализацию IEnumerator, которая распознает индексы, И реализуете конструкцию foreach, которая распознает эту пользовательскую реализацию.
лично я бы создал такой метод расширения
public static class Ext
{
public static void FE<T>(this IEnumerable<T> l, Action<int, T> act)
{
int counter = 0;
foreach (var item in l)
{
act(counter, item);
counter++;
}
}
}
и используйте это так
var x = new List<string>() { "hello", "world" };
x.FE((ind, ele) =>
{
Console.WriteLine($"{ind}: {ele}");
});
это также позволяет избежать ненужных выделений, замеченных в других ответах.
Таким образом, вы можете использовать индекс и значение с помощью LINQ:
ListValues.Select((x, i) => new { Value = x, Index = i }).ToList().ForEach(element =>
{
// element.Index
// element.Value
});
//using foreach loop how to get index number:
foreach (var result in results.Select((value, index) => new { index, value }))
{
//do something
}
Хотя этот код может ответить на вопрос, предоставляя дополнительный контекст относительно как и / или Почему, он решает проблему, что улучшит долгосрочную ценность ответа.
Получение приведения foreach обычно не является для меня более оптимизированным, чем просто использование доступа к коллекции на основе индекса, хотя во многих случаях он будет равным. Цель foreach - сделать ваш код читабельным, но он (обычно) добавляет уровень косвенности, что не является бесплатным.