Менее сложный способ обработки первого прохода через foreach?

Я часто обнаруживаю, что в цикле foreach я делаю следующее индекс-счетчик беспорядок, чтобы узнать, есть ли я в первом элементе или нет. Есть ли более элегантный способ сделать это в С#, что-то вроде строк if(this.foreach.Pass == 1) и т.д.?

int index = 0;
foreach (var websitePage in websitePages) {
 if(index == 0)
 classAttributePart = " class=\"first\"";
 sb.AppendLine(String.Format("<li" +="" classattributepart="" "="">" + 
 "<a href="\" {0}\""="">{1}</a>", 
 websitePage.GetFileName(), websitePage.Title));
 index++;
}
</li">
12 ответов

Другой подход заключается в том, чтобы признать, что "уродливая часть" должна быть реализована где-то и обеспечить абстракцию, которая скрывает "уродливую часть", так что вам не нужно повторять ее в нескольких местах и ​​может сфокусироваться на конкретном алгоритме, Это можно сделать с помощью выражений лямбда С# (или с помощью анонимных делегатов С# 2.0, если вы ограничены .NET.NET):

void ForEachWithFirst<t>(IEnumerable<t> en, 
 Action<t> firstRun, Action<t> nextRun) {
 bool first = true;
 foreach(var e in en) {
 if (first) { first = false; firstRun(e); } else nextRun(e);
 }
}
</t></t></t></t>

Теперь вы можете использовать этот метод повторного использования для реализации своего алгоритма следующим образом:

ForEachWithFirst(websitePages,
 (wp => sb.AppendLine(String.Format("<li first\""="">" +
 "<a href="\" {0}\""="">{1}</a></li>", wp.GetFileName(), wp.Title)))
 (wp => sb.AppendLine(String.Format("<li>" + 
 "<a href="\" {0}\""="">{1}</a></li>", wp.GetFileName(), wp.Title))) );

Вы можете проектировать абстракцию по-разному в зависимости от точного повторяющегося шаблона. Хорошо, что благодаря лямбда-выражению - структура абстракции полностью зависит от вас.


Немного менее подробный:

string classAttributePart = " class=\"first\"";
foreach (var websitePage in websitePages)
{
 sb.AppendLine(String.Format("<li" +="" classattributepart="" "=""><a href="\" {0}\""="">{1}</a>", websitePage.GetFileName(), websitePage.Title));
 classAttributePart = string.Empty;
}
</li">

Если вы используете .NET 3.5, вы можете использовать перегрузку Select, которая дает вам индекс и тестирует это. Тогда вам также не понадобится StringBuilder. Вот код для этого:

string[] s = websitePages.Select((websitePage, i) =>
 String.Format("<li{0}><a href="\" {1}\""="">{2}</a>\n",
 i == 0 ? " class=\"first\"" : "",
 websitePage.GetFileName(),
 websitePage.Title)).ToArray();
string result = string.Join("", s);
</li{0}>

Это выглядит немного более многословным, но в основном потому, что я сломал очень длинную строку на несколько более коротких.


Вы можете использовать цикл for вместо цикла foreach. В этом случае цикл for может начинать его с индекса 1, и вы можете сделать первый элемент вне цикла, если длина больше 0.

По крайней мере, в этом случае вы не будете делать дополнительное сравнение на каждой итерации.


if (websitePages.IndexOf(websitePage) == 0)
 classAttributePart = " class=\"last\"";

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


Это может быть немного лучше

bool doInit = true;
foreach (var websitePage in websitePages)
{
 if (doInit)
 {
 classAttributePart = " class=\"first\"";
 doInit = false;
 }
 sb.AppendLine(String.Format("<li" +="" classattributepart="" "=""><a href="\" {0}\""="">{1}</a>", websitePage.GetFileName(), websitePage.Title));
}
</li">

В конечном итоге я тоже очень много делаю, и это тоже меня тоже беспокоит.


Другим подходом было бы использовать jQuery first selector, чтобы установить класс вместо кода на стороне сервера.

$(document).ready(function(){ 
 $("#yourListId li:first").addClass("first");
}


Если вам интересен только первый элемент, лучшим (как в большинстве читаемых) способом является использование LINQ для определения того, какой элемент является первым. Вот так:

var first = collection.First();
// do something with first element
....
foreach(var item in collection){
 // do whatever you need with every element
 ....
 if(item==first){
 // and you can still do special processing here provided there are no duplicates
 }
}

Если вам нужно числовое значение индекса или не первый индекс, вы всегда можете сделать

foreach (var pair in collection.Select((item,index)=>new{item,index}))
{
 // do whatever you need with every element
 ....
 if (pair.index == 5)
 {
 // special processing for 5-th element. If you need to do this, your design is bad bad bad
 }
}

PS И самый лучший способ - использовать for -loop. Используйте foreach, только если for недоступен (т.е. Коллекция IEnumerable, а не список или что-то еще)


Вы можете сделать это до цикла foreach, если вы делаете это только для первого индекса.


Как насчет этого?

var iter = websitePages.GetEnumerator();
iter.MoveNext();
//Do stuff with the first element
do {
 var websitePage = iter.Current;
 //For each element (including the first)...
} while (iter.MoveNext());


Как мой папа любит говорить "Использовать правильный инструмент для работы".

В этом случае, посмотрите, что нужно сделать вашему циклу.

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

В противном случае, чтобы ответить на ваш вопрос, похоже, нет легкого не-многословного средства для идентификации индекса элемента списка, поскольку он обращается в цикле foreach.


public static class ExtenstionMethods
{
 public static IEnumerable<************<int32, t="">> Indexed<t>(this IEnumerable<t> collection)
 {
 Int32 index = 0;
 foreach (var value in collection)
 {
 yield return new ************<int32, t="">(index, value);
 ++index;
 }
 }
}
foreach (var iter in websitePages.Indexed())
{
 var websitePage = iter.Value;
 if(iter.Key == 0) classAttributePart = " class=\"first\"";
 sb.AppendLine(String.Format("<li" +="" classattributepart="" "=""><a href="\" {0}\""="">{1}</a>", websitePage.GetFileName(), websitePage.Title));
}
</li"></int32,></t></t></************<int32,>


В этом примере вы можете избавиться от проверки индекса таким образом.

foreach (var websitePage in websitePages) 
{ 
 classAttributePart = classAttributePart ?? " class=\"first\""; 
 sb.AppendLine(String.Format("<li" +="" classattributepart="" "=""><a href="\" {0}\""="">{1}</a>", websitePage.GetFileName(), websitePage.Title)); 
} 
</li">

Было бы лучше проверить результирующую переменную данных для выполнения этой задачи. В этом случае он проверяет значение null в строке classAttributePart и добавляет начальное значение.

licensed under cc by-sa 3.0 with attribution.