Менее подробный способ обработки первого прохода через foreach? - PullRequest
22 голосов
/ 22 февраля 2010

Я часто обнаруживаю, что делаю следующее беспорядок счетчика индексов в цикле foreach, чтобы выяснить, нахожусь ли я на первом элементе или нет. Есть ли более элегантный способ сделать это в C # , что-то вроде 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++;
}

Ответы [ 12 ]

13 голосов
/ 22 февраля 2010

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

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

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

11 голосов
/ 22 февраля 2010

Немного менее многословно:

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

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

4 голосов
/ 22 февраля 2010

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

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

4 голосов
/ 22 февраля 2010

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

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

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

4 голосов
/ 22 февраля 2010
if (websitePages.IndexOf(websitePage) == 0)
    classAttributePart = " class=\"last\"";

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

3 голосов
/ 22 февраля 2010

Другой подход заключается в использовании первого селектора jQuery для установки класса вместо кода на стороне сервера.

$(document).ready(function(){ 
     $("#yourListId li:first").addClass("first");
}
2 голосов
/ 22 февраля 2010

Если вас интересует только элемент 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, а не список или что-то в этом роде)

2 голосов
/ 22 февраля 2010

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

1 голос
/ 22 февраля 2010

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

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());
0 голосов
/ 22 февраля 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...