C # поліпшення foreach?


9

Я часто стикаюся з цим під час програмування, коли мені хочеться, щоб індекс кількості циклів був у foreach і мав створити ціле число, використовувати його, приріст тощо. Чи не було б гарною ідеєю, якби було введене ключове слово, яке було кількість петлі всередині передпліччя? Його також можна використовувати в інших петлях.

Це суперечить використанню ключових слів, розуміючи, як компілятор не дозволив би це ключове слово використовувати ніде, а не в контурній конструкції?


7
Ви можете зробити це самостійно методом розширення, дивіться відповідь від Dan Finch: stackoverflow.com/a/521894/866172 (не найкраща відповідь, але я думаю, що це "чистіша")
Jalayn

Перл та інше, як $ _ приходить на думку. Я ненавиджу цей матеріал.
Ям Маркович

2
З того, що я знаю про C #, він має багато контекстних ключових слів, тому це не може "суперечити використанню ключових слів". Як зазначено у відповіді нижче, однак, ви, мабуть, просто хочете використовувати for () цикл.
Craige

@Jalayn Будь ласка, опублікуйте це як відповідь. Це один з найкращих підходів.
Апоорв Хурасія

@MonsterTruck: Я не думаю, що він повинен. Справжнім рішенням було б перенести це питання на SO та закрити його як дублікат питання, на яке Джалайн посилається.
Брайан

Відповіді:


54

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


1
+1 - хороший бал. Досить просто використовувати IEnumerable.Count або object.length з регулярною для циклу, щоб уникнути будь-яких проблем із з'ясуванням кількості елементів в об'єкті.

11
@ GlenH7: Залежно від реалізації, це IEnumerable.Countможе бути дорогим (або неможливим, якщо це лише один раз перерахування). Вам потрібно знати, що є основним джерелом, перш ніж ви могли сміливо це зробити.
Бінарний страшник

2
-1: Білл Вагнер в більш ефективні C # описує , чому один повинен завжди дотримуватися foreachбільш for (int i = 0…). Він написав пару сторінок, але я можу підсумувати два слова - оптимізація компілятора на ітерації. Добре, що не було двох слів.
Апоорв Хурасія

13
@MonsterTruck: все, що писав Білл Вагнер, я бачив достатньо ситуацій, як описана в ОП, де 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 # над класичним для циклу, очевидно, має дивну думку щодо читабельності. Це може бути акуратний трюк, але я не дозволю щось подібне у коді якості виробництва. Це набагато шумніше, ніж класичне для циклу, і його не можна зрозуміти як швидко.
Сокіл

4
@Falcon, це дійсна альтернатива, ЯКЩО НЕ МОЖЕТЕ ВИКОРИСТИТИ стару моду на цикл (на який потрібно порахувати). Ось декілька колекцій об'єктів, над якими мені довелося
обійтись

8
@FabricioAraujo: Напевно, для C # для циклів не потрібно рахувати. Наскільки я знаю, вони точно такі ж, як і C для циклів, це означає, що ви можете використовувати будь-яке булеве значення для завершення циклу. Наприклад, перевірка кінця перерахування, коли MoveNext повертає значення false.
Зан Лінкс

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

2
Це досить розумно, але я думаю, що використання нормального для "читабельніше", особливо якщо в дорозі хтось, хто закінчує підтримання коду, може бути не дуже тузом у .NET і не надто знайомий з концепцією методів розширення . Я все ще захоплюю цей маленький код. :)
MetalMikester

1
Чи можна заперечити будь-яку сторону цього питання і оригінальне запитання - я згоден з наміром плакатів, бувають випадки, коли передбачення читається краще, але потрібен індекс, але я завжди несприятливий для додавання в мову більше синтаксичного цукру, якщо це не так дуже потрібне - у мене точно таке ж рішення у власному коді з іншою назвою - 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(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

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


4
+1: мало хто пам’ятає, що передбачення - це в основному синтаксичний цукор за схемою ітератора.
Стівен Еверс

1
Ну є деякі відмінності; маніпуляція з покажчиком, де присутні в .NET приховано досить глибоко від програміста, але ви можете написати цикл, який дуже схожий на приклад 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++;
}

+1, також, якщо ви використовуєте індекс точно один раз всередині циклу, ви можете зробити щось на кшталт foo(++i)або foo(i++)залежно від того, який індекс потрібен.
Робота

2

Якщо все, що ви знаєте, це те, що колекція є IEnumerable, але вам потрібно відслідковувати кількість елементів, які ви обробили до цього часу (і, таким чином, загальну кількість, коли ви закінчите), ви можете додати пару рядків до основної для циклу:

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
}

Ви також можете додати до індексу нарощену змінну індексу:

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

1

Оцінка масштабів також працює, якщо ви хочете захистити блок від зіткнень з іншими іменами ...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }

-1

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.