Edward Tanguay
Edward Tanguay

Reputation: 193462

Less-verbose way of handling the first pass through a foreach?

I often find myself doing the following index-counter messiness in a foreach loop to find out if I am on the first element or not. Is there a more elegant way to do this in C#, something along the lines of if(this.foreach.Pass == 1) etc.?

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

Upvotes: 22

Views: 1130

Answers (12)

Nevermind
Nevermind

Reputation: 1551

If you're interested in the first element only, the best (as in most readable) way is to use LINQ to find out what element is the first. Like this:

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

If you need numerical value of the index, or non-first index, you can always do

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 And the very best way is to use a for-loop. Use foreach only if for is not available (i.e. the collection is IEnumerable and not a list or something)

Upvotes: 2

RameshVel
RameshVel

Reputation: 65887

In this example you can get rid of the index check this way.

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

It would be better to check on the resultant data variable to perform this kind of task. In this case It checks for null of the classAttributePart string and append the initial value.

Upvotes: 0

FrantzX
FrantzX

Reputation: 1

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

Upvotes: 0

S.Robins
S.Robins

Reputation: 703

As my dad likes to say, "Use the right tool for the job".

In this case, look at what it is your loop needs to do.

  • If it's something that can be done outside the loop, move it and your use of foreach is fine.
  • If you need to handle a complex combination of cases, then you may want to consider using a different pattern (eg: a state pattern, or whatever is most appropriate) for the stuff you want to do inside loop, in which case you choose the loop construct that makes the most sense at the time.
  • If your loop is dependant on being able to extract the iteration index out to pass it elsewhere, then perhaps a for loop may be a better choice.

Otherwise, to answer you question, there doesn't appear to be an easy non-verbose means to identify the index of a list item as it is accessed in a foreach loop.

Upvotes: 0

jrummell
jrummell

Reputation: 43117

Another approach would be to use jQuery's first selector to set the class instead of server side code.

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

Upvotes: 3

Tomas Petricek
Tomas Petricek

Reputation: 243096

Another approach is to accept that the "ugly part" has to be implemented somewhere and provide an abstraction that hides the "ugly part" so that you don't have to repeat it in multiple places and can focus on the specific algorithm. This can be done using C# lambda expressions (or using C# 2.0 anonymous delegates if you're restricted to .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);
  }
}

Now you can use this reusable method to implement your algorithm like this:

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

You could design the abstraction differently depending on the exact repeating pattern. The good thing is that - thanks to lambda expression - the structure of the abstraction is completely up to you.

Upvotes: 13

Mark Byers
Mark Byers

Reputation: 839184

Slightly less verbose:

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

If you are using .NET 3.5 you could use the overload of Select that gives you the index and test that. Then you also wouldn't need the StringBuilder. Here's the code for that:

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

It looks a bit more verbose, but that's mainly because I broke the very long line many shorter ones.

Upvotes: 11

cpx
cpx

Reputation: 17577

You can do it before the foreach loop if your only doing it for the first index.

Upvotes: 2

Brian R. Bondy
Brian R. Bondy

Reputation: 347586

You could use a for loop instead of a foreach loop. In that case your for loop could start it's index at 1 and you could do the first element outside of the loop if the length is more than 0.

At least in this case you wouldn't be doing an extra comparison on each iteration.

Upvotes: 4

Anon.
Anon.

Reputation: 60043

How about this?

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

Upvotes: 1

John Knoeller
John Knoeller

Reputation: 34218

This might be slightly better

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

I end up doing this sort of thing a lot too, and it bugs me also.

Upvotes: 4

Joel
Joel

Reputation: 19378

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

This may be more elegant, but it will probably be less performant since it has to check the index of each element.

Upvotes: 4

Related Questions