Reputation: 193462
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
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
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
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
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.
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
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
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
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
Reputation: 17577
You can do it before the foreach loop if your only doing it for the first index.
Upvotes: 2
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
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
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
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