C # foreach улучшения?

9

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

Идет ли это против использования ключевых слов, поскольку компилятор не позволяет использовать это ключевое слово где-либо, кроме как в циклической конструкции?

Джастин
источник
7
Вы можете сделать это самостоятельно с помощью метода расширения, см. Ответ Дэна Финча: stackoverflow.com/a/521894/866172 (не самый лучший ответ, но я думаю, что он «чище»)
Jalayn
На ум приходит Perl и все такое, как $ _. Я ненавижу такие вещи.
Ям Маркович
2
Из того, что я знаю о C #, у него много контекстно-ориентированных ключевых слов, так что это не "противоречит использованию ключевых слов". Однако, как указано в ответе ниже, вы, вероятно, просто хотите использовать for () цикл.
Крейг,
@Jalayn Пожалуйста, опубликуйте это как ответ. Это один из лучших подходов.
Апурв Хурасия
@MonsterTruck: я не думаю, что он должен. Реальным решением было бы перенести этот вопрос в SO и закрыть его как дубликат вопроса, на который ссылается Jalayn.
Брайан

Ответы:

54

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

Ryathal
источник
1
+1 - хороший момент. Достаточно просто использовать IEnumerable.Count или object.length с регулярным циклом for, чтобы избежать проблем с определением количества элементов в объекте.
11
@ GlenH7: В зависимости от реализации, IEnumerable.Countможет быть дорогим (или невозможным, если это только один раз). Вы должны знать, что является основным источником, прежде чем вы сможете безопасно это сделать.
Двоичный беспорядок
2
-1: Билл Вагнер в « Более эффективном C #» описывает, почему всегда следует придерживаться foreachболее for (int i = 0…). Он написал пару страниц, но я могу подвести итог в двух словах - оптимизация компилятора на итерации. Хорошо, это не два слова.
Апурв Хурасия
13
@MonsterTruck: что бы ни писал Билл Вагнер, я видел достаточно ситуаций, подобных описанной в OP, где forцикл дает более читаемый код (а теоретическая оптимизация компилятора часто является преждевременной оптимизацией).
Док Браун
1
@DocBrown Это не так просто, как преждевременная оптимизация. Я сам определил узкие места в прошлом и исправил их, используя этот подход (но я признаю, что имел дело с тысячами объектов в коллекции). Надеюсь вскоре опубликовать рабочий пример. Тем временем вот Джон Скит . [Он говорит, что повышение производительности не будет значительным, но это во многом зависит от коллекции и количества объектов].
Апурв Хурасия
37

Вы можете использовать анонимные типы, как это

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

Это похоже на enumerateфункцию Python

for i, v in enumerate(items):
    print v, i
Крис Харпер
источник
+1 Ох ... лампочка. Мне нравится такой подход. Почему я никогда не думал об этом раньше?
Фил
17
Тот, кто предпочитает это в C # по сравнению с классическим циклом for, явно имеет странное мнение о читабельности. Это может быть изящный трюк, но я бы не допустил такого в коде качества производства. Он намного шумнее классического цикла for и не может быть понят как быстрый.
Сокол
4
@Falcon, это верная альтернатива, ЕСЛИ ВЫ НЕ МОЖЕТЕ ИСПОЛЬЗОВАТЬ старую моду для цикла (для которой необходим счет). Вот несколько коллекций объектов, с которыми мне приходилось работать ...
Fabricio Araujo
8
@FabricioAraujo: Конечно, C # для циклов не требует подсчета. Насколько я знаю, они точно такие же, как C для циклов, что означает, что вы можете использовать любое логическое значение для завершения цикла. Например, проверка конца перечисления, когда MoveNext возвращает false.
Zan Lynx
1
Это дает дополнительное преимущество, заключающееся в том, что весь код обрабатывает приращение в начале блока foreach вместо того, чтобы находить его.
JeffO
13

Я просто добавляю ответ Дана Финча здесь, как и просил.

Не дай мне очков, дай очки Дану Финчу :-)

Решением является написание следующего общего метода расширения:

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

Который используется так:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );
Jalayn
источник
2
Это довольно умно, но я думаю, что просто использование обычного for более «читабельно», особенно если в будущем тот, кто заканчивает поддерживать код, может быть не слишком опытным в .NET и не слишком знаком с концепцией методов расширения. , Я все еще беру этот маленький кусочек кода. :)
MetalMikester
1
Могли бы поспорить обе стороны этого, и первоначальный вопрос - я согласен с намерением авторов, есть случаи, когда foreach читается лучше, но нужен индекс, но я всегда против добавления синтаксического сахара в язык, если это не так. крайне необходимо - у меня точно такое же решение в моем собственном коде с другим именем - strings.ApplyIndexed <T> (......)
Марк Муллин
Я согласен с Марком Маллином, вы можете поспорить с любой стороны. Я думал, что это было довольно умное использование методов расширения, и это соответствовало вопросу, поэтому я поделился им.
Джалайн
5

Я могу ошибаться по этому поводу, но я всегда думал, что основной целью цикла foreach было упрощение итерации со дней STL в C ++, т.е.

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

сравните это с использованием .NET IEnumerable в foreach

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

последнее намного проще и позволяет избежать многих старых ошибок. Кроме того, они, кажется, следуют почти одинаковым правилам. Например, вы не можете изменить содержимое IEnumerable внутри цикла (что имеет гораздо больше смысла, если вы думаете об этом в стиле STL). Однако цикл for часто имеет другое логическое назначение, чем итерация.

Джонатан Хенсон
источник
4
+1: мало кто помнит, что foreach - это, по сути, синтаксический сахар над шаблоном итератора.
Стивен Эверс
1
Ну, есть некоторые различия; Манипулирование указателем, если оно присутствует в .NET, скрыто от программиста, но вы можете написать цикл for, который очень похож на пример C ++:for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS
@KeithS, если вы не понимаете, что все, к чему вы прикасаетесь в .NET, кроме структурированного типа, является указателем, просто с другим синтаксисом (. Вместо ->). Фактически, передача ссылочного типа в метод - это то же самое, что передача его в качестве указателя, а передача по ссылке - в виде двойного указателя. То же самое. По крайней мере, так думает человек с родным языком C ++. Вроде как, вы учите испанский в школе, думая о том, как язык синтаксически связан с вашим родным языком.
Джонатан Хенсон
* тип значения не структурированный тип. Извините.
Джонатан Хенсон
2

Если рассматриваемая коллекция является IList<T>, вы можете просто использовать forс индексацией:

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

Если нет, то вы могли бы использовать Select(), это имеет перегрузку, которая дает вам индекс.

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

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}
svick
источник
+1, также, если вы используете индекс ровно один раз внутри цикла, вы можете сделать что-то вроде foo(++i)или в foo(i++)зависимости от того, какой индекс нужен.
Работа
2

Если все, что вы знаете, это то, что коллекция является IEnumerable, но вам нужно отслеживать, сколько элементов вы обработали до сих пор (и, следовательно, общее количество, когда вы закончите), вы можете добавить пару строк в базовый цикл for:

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

Вы также можете добавить инкрементную переменную индекса в foreach:

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

Если вы хотите, чтобы это выглядело более элегантно, вы можете определить метод расширения или два, чтобы «скрыть» эти детали:

public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
{
    foreach(T elem in input)
        action(elem);
}

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));
Keiths
источник
1

Scoping также работает, если вы хотите сохранить блок в чистоте от столкновений с другими именами ...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }
сойка
источник
-1

Для этого я использую whileцикл. Циклы forтакже работают, и многие люди предпочитают их, но мне нравится использовать только forто, что будет иметь фиксированное количество итераций. IEnumerables может генерироваться во время выполнения, так что, на мой взгляд, whileимеет больше смысла. Назовите это неофициальное руководство по кодированию, если хотите.

Robotman
источник