Менее сложный способ обработки первого прохода через 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></li>",
websitePage.GetFileName(), websitePage.Title));
index++;
}
Ответы
Ответ 1
Другой подход заключается в том, чтобы признать, что "уродливая часть" должна быть реализована где-то и обеспечить абстракцию, которая скрывает "уродливую часть", так что вам не нужно повторять ее в нескольких местах и может сфокусироваться на конкретном алгоритме, Это можно сделать с помощью выражений лямбда С# (или с помощью анонимных делегатов С# 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);
}
}
Теперь вы можете использовать этот метод повторного использования для реализации своего алгоритма следующим образом:
ForEachWithFirst(websitePages,
(wp => sb.AppendLine(String.Format("<li class=\"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))) );
Вы можете проектировать абстракцию по-разному в зависимости от точного повторяющегося шаблона. Хорошо, что благодаря лямбда-выражению - структура абстракции полностью зависит от вас.
Ответ 2
Немного менее подробный:
string classAttributePart = " class=\"first\"";
foreach (var websitePage in websitePages)
{
sb.AppendLine(String.Format("<li" + classAttributePart + "><a href=\"{0}\">{1}</a></li>", websitePage.GetFileName(), websitePage.Title));
classAttributePart = string.Empty;
}
Если вы используете .NET 3.5, вы можете использовать перегрузку Select, которая дает вам индекс и тестирует это. Тогда вам также не понадобится StringBuilder. Вот код для этого:
string[] s = websitePages.Select((websitePage, i) =>
String.Format("<li{0}><a href=\"{1}\">{2}</a></li>\n",
i == 0 ? " class=\"first\"" : "",
websitePage.GetFileName(),
websitePage.Title)).ToArray();
string result = string.Join("", s);
Это выглядит немного более многословным, но в основном потому, что я сломал очень длинную строку на несколько более коротких.
Ответ 3
if (websitePages.IndexOf(websitePage) == 0)
classAttributePart = " class=\"last\"";
Это может быть более элегантно, но, вероятно, будет менее результативным, поскольку он должен проверять индекс каждого элемента.
Ответ 4
Это может быть немного лучше
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></li>", websitePage.GetFileName(), websitePage.Title));
}
В конечном итоге я тоже очень много делаю, и это тоже меня тоже беспокоит.
Ответ 5
Вы можете использовать цикл for
вместо цикла foreach
. В этом случае цикл for может начинать его с индекса 1, и вы можете сделать первый элемент вне цикла, если длина больше 0.
По крайней мере, в этом случае вы не будете делать дополнительное сравнение на каждой итерации.
Ответ 6
Другим подходом было бы использовать jQuery first selector, чтобы установить класс вместо кода на стороне сервера.
$(document).ready(function(){
$("#yourListId li:first").addClass("first");
}
Ответ 7
Вы можете сделать это до цикла foreach, если вы делаете это только для первого индекса.
Ответ 8
Если вам интересен только первый элемент, лучшим (как в большинстве читаемых) способом является использование 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
, а не список или что-то еще)
Ответ 9
Как насчет этого?
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());
Ответ 10
Как мой папа любит говорить "Использовать правильный инструмент для работы".
В этом случае, посмотрите, что нужно сделать вашему циклу.
- Если это что-то, что можно сделать за пределами цикла, переместите его, и ваше использование foreach в порядке.
- Если вам нужно обработать сложную комбинацию случаев, тогда вам может потребоваться использовать другой шаблон (например: шаблон состояния или что-то самое подходящее) для материала, который вы хотите сделать внутри цикла, и в этом случае вы выбираете конструкцию цикла, которая имеет наибольший смысл в то время.
- Если ваш цикл зависит от возможности извлечь индекс итерации, чтобы передать его в другом месте, возможно, лучший цикл может быть в цикле for.
В противном случае, чтобы ответить на ваш вопрос, похоже, нет легкого не-многословного средства для идентификации индекса элемента списка, поскольку он обращается в цикле foreach.
Ответ 11
public static class ExtenstionMethods
{
public static IEnumerable<KeyValuePair<Int32, T>> Indexed<T>(this IEnumerable<T> collection)
{
Int32 index = 0;
foreach (var value in collection)
{
yield return new KeyValuePair<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></li>", websitePage.GetFileName(), websitePage.Title));
}
Ответ 12
В этом примере вы можете избавиться от проверки индекса таким образом.
foreach (var websitePage in websitePages)
{
classAttributePart = classAttributePart ?? " class=\"first\"";
sb.AppendLine(String.Format("<li" + classAttributePart + "><a href=\"{0}\">{1}</a></li>", websitePage.GetFileName(), websitePage.Title));
}
Было бы лучше проверить результирующую переменную данных для выполнения этой задачи.
В этом случае он проверяет значение null в строке classAttributePart и добавляет начальное значение.