.NET Framework - Last row in foreach loop

Asked By tshad on 29-Feb-08 04:02 PM
Is there a way to know if you are looking at the last record record of
foreach loop other then setting up a loop counter that you manually
increment?

foreach (Racecar racecar in RaceCarCollection)
{
...

if last row do something?
}

You can tell how many items you have in the collection but is there way to
tell which row you are looking or if this is the last row?

Thanks,

Tom




bpbro replied on 29-Feb-08 04:16 PM
tshad skrev:
Not without counying an test against RaceCarCollection.Count (or .Length)

Why not use a for loop instead?


--
Bjørn Brox
zack replied on 01-Mar-08 03:43 PM
Not as far as I am aware of. I have done someting like this with:

foreach (int i =3D 0; i < collection.Count; i++)
{
object =3D collection[i];
...
if (i =3D=3D collection.Count - 1)
do something to the last object
}
pbromber replied on 29-Feb-08 04:21 PM
Doubt it. I've always use a counter variable and compared to the length of
the collection. Not a big deal.
-- Peter
Site: http://www.eggheadcafe.com
UnBlog: http://petesbloggerama.blogspot.com
Short Urls & more: http://ittyurl.net
Rene replied on 29-Feb-08 04:27 PM
Besides what others have mentioned, here is another way to tackle your
problem without having to have a counter in case you had something against
counters.


Racecar lastRacecar;
foreach (Racecar racecar in RaceCarCollection)
{
lastRacecar = racecar;

// Do something...........
}
if(lastRacecar != null)
{
// Do last row stuff here...........
}
christer replied on 01-Mar-08 03:43 PM
Sounds like you should test doing that with a database, the price for
MySQL or if you want to go M$ SQLserverExpress is nice (0$), a
pseudostatement like "readline(); if next line is EOF..." is not too
hard to program logically but you get double the IO, and for
nothing..  or as mentioned check the length..
//CY
zack replied on 01-Mar-08 03:43 PM
to

Sorry, that should be for instead of foreach.
Darren replied on 29-Feb-08 04:48 PM
You can get the enumerator then use Reset, Current, & MoveNext but it's ugly.

IEnumerator<RaceCar> e = RaceCarCollection.GetEnumerator();
e.Reset();
bool isValid = e.MoveNext();
while (isValid)
{
RaceCar car = e.Current;
isValid = e.MoveNext();
if ( !isValid )
{
// this is the last car
}
}
Jon Skeet [C# MVP] replied on 29-Feb-08 08:08 PM
There is using "SmartEnumerable" from my (free) MiscUtil library:

http://pobox.com/~skeet/csharp/miscutil/usage/smartenumerable.html

(Note that in order to do that, it's always really a row ahead of where
it reports. Irrelevant in most cases though.)

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk
FamilyTreeMik replied on 29-Feb-08 08:50 PM
Won't the following work?  I cannot say it is efficient...

if (racecar == RaceCarCollection [RaceCarCollection.count - 1])
{
// do something...
}
Jon Skeet [C# MVP] replied on 01-Mar-08 03:43 PM
That entirely depends on the type of RaceCarCollection. If it is just
an IEnumerable<T>, it does not have a Count property.

Jon
sore replied on 01-Mar-08 04:20 AM
=20

Nice :)
'When C# 3 is released, this pattern will be made even simpler with'

Any chance for that update ? :-)

See ya
S=F8ren Reinke
Peter Morris replied on 01-Mar-08 04:35 AM
What occurs to me is that you only want something done once, so why put it
in the loop at all?

foreach (RaceCar currentRaceCar in RaceCarCollection)
currentRaceCar.Start();

RaceCarCollection[RaceCarCollection.Count - 1].BreakDown();
Jon Skeet [C# MVP] replied on 01-Mar-08 07:25 AM
The implicit typing is something I don't need to do anything with, but=20
I'll put in the extension method when I get a bit of time - thanks for=20
the reminder :)

--=20
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk
sore replied on 01-Mar-08 08:29 AM
No problem.

I just enjoy getting tips from your website :-)

See ya
S=F8ren Reinke
FamilyTreeMik replied on 01-Mar-08 08:44 AM
I see your point in earlier versions, but under 2008, IEnumerable<T>
supports a Count() method, but not a property.  The original poster said they
knew the count though.
Jon Skeet [C# MVP] replied on 01-Mar-08 09:56 AM
Well, it sort of does. It has an extension method of Count() - but
unless the iterator is actually an IList it's going to retrieve that
count by iterating through the whole collection. Not exactly efficient.


Ah, true.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk
tshad replied on 01-Mar-08 07:43 PM
I could.

But it wasn't a problem just a question.

At the moment, I just set iktr= 0 and just a line after the foreach line
(iktr++;) and then test against RaceCarCollection.Count as you mentioned.

I was just curious if there was a property somewhere that told you what line
in the collection  you were on without having to do this.

Thanks,

Tom
tshad replied on 01-Mar-08 07:47 PM
Something like this is fine but I might just as well do:

int iKtr = 0;
foreach (Racecar racecar in RaceCarCollection)
{
iKtr++;
...

if( iKtr == RaceCarCollection.Count)
do something?
}

Thanks,

Tom
tshad replied on 01-Mar-08 07:48 PM
I do the same.  And as you said, no big deal.

I was just curious if there was a better way.

Thanks,

Tom
tshad replied on 01-Mar-08 07:50 PM
Huh?

How would racecar ever be null?

lastRaceCar would always be equal to the current row.

Tom
tshad replied on 01-Mar-08 07:53 PM
Actually, that might work.

Thanks,

Tom
tshad replied on 01-Mar-08 07:56 PM
What does BreakDown do?

Thanks,

Tom
Peter Duniho replied on 01-Mar-08 08:08 PM
You're right, it wouldn't.  Not with the code written that way.  Of  =

course, with it written that way, it won't compile either because  =


If you fix the compiler error and assign null to the local variable befo=
re  =

the foreach() loop, then the variable will be null if your collection is=
=

empty.


What's its value if there is no "current row"?

If the code is written such that it compiles, the value is "null".

Pete
tshad replied on 02-Mar-08 03:19 AM
Right.

It would have to be:

Racecar lastRacecar = null;

as you mention below.

Yes, but in my case, I was only assuming that there was at least one item in
the collection and I wanted to make sure the last line gets processed
because I could be accumulating something and in the last row I need to make
sure I process it - before leaving the loop.

If you are inside the loop (at least one item), there would be a current
row.


Only if there is no items in collection.

Thanks,

Tom
Peter Duniho replied on 02-Mar-08 05:56 AM
Nothing about what you wrote causes me to believe that tracking the last
item as suggested by Rene would not work.  To "accumulate" something, you
must be using a variable declared outside the loop anyway, and if at the
end of the loop you intend to do something with that "accumulated"
something relative to the last item enumerated in the loop, you can simply
do that outside of the loop, per Rene's suggestion.

It's possible Rene's suggestion wouldn't work for you, but if so you
haven't explained why it wouldn't with the paragraph I quoted above.

As far as your comment about the check for null goes, the code Rene posted
was simply being cautious.  You may decide to make the assumption that the
variable will never be null, and thus leave that check out.  I personally
wouldn't make the assumption, but you're free to do so if you like.
Whether you do or not doesn't change the validity of the suggestion.

Pete
bpbro replied on 02-Mar-08 09:21 AM
tshad skrev:

Depends on what kind of object you have and how it is organized.
In your example: Is Racecar.Index an option?



--
Bjørn Brox
tshad replied on 02-Mar-08 12:38 PM
I do not know.

What is that?

Tom.
tshad replied on 02-Mar-08 12:52 PM
I agree.  I may not have been clear on that issue. I wasn't being critical
here, just trying to understand it.

In my example, I want to be able to do something on the last loop and
process it (as I do on the previous loops) without having to rewrite the
processing code outside of the loop:

int ktr = 0;
foreach (Racecar racecar in RaceCarCollection)
{
ktr++;
// Do something...........

if(ktr == RaceCarCollection.Count)
{
// Do last row stuff here...........
}
// Do something else
}

As Rene said I could put the null test outside of the foreach loop, but then
I have to do the "// Do something else" code outside as well.

That's true.

Thanks,

Tom
Peter Duniho replied on 02-Mar-08 02:45 PM
he

So, if I understand correctly, not only do you want to do something  =

special when you reach the last element of the loop, the processing of  =

that last element is supposed to incorporate that "something special".

In that case, yes...there's a benefit to keeping that inside the loop an=
d  =

tracking a counter may be a better solution.  Noting, of course, that  =

that's mainly because you've said you do have a valid count for the  =

collection and so it's reasonable to make that test without any addition=
al  =

work.

One remaining point: if your processing is some significant amount of co=
de  =

(more than a couple of lines, for example), then I think it would be  =

almost as reasonable to put the processing into a separate method and th=
en  =

call it from two places: inside the loop, and then at the end.

I say "almost", because doing that actually makes the loop more awkward.=
=

It'd have to look something like this:

Racecar racecarPrev =3D null;

foreach (Racecar racecar in RaceCarCollection)
{
if (racecarPrev !=3D null)
{
ProcessItem(racecarPrev);
}

racecarPrev =3D racecar;
}

if (racecarPrev !=3D null)
{
// do "something special"
ProcessItem(racecarPrev);
}

That way you don't do the "ProcessItem" work until you know whether the =
=

item is in fact the last one or not.  Since the processing is encapsulat=
ed  =

in a separate method, I think it's less of a problem to duplicate that  =

call (I definitely don't like copy-and-pasting code that does real work,=
=

but duplicating a simple call to a method isn't so bad if it's otherwise=
=

beneficial).

Obviously you wouldn't contort your code to work like the above if you  =

already have a valid count for your collection and can do the "last item=

test inside the loop.  But it could be worth keeping the above arrangeme=
nt  =

in mind for dealing with collections that don't expose a count.

Pete
Peter Duniho replied on 02-Mar-08 03:39 PM
I think Bjørn is asking whether your Racecar class itself keeps track of
its own position within the collection.

Some collections are like this.  The item in the collection can only be in
one collection at a time, and it does know its own position within the
collection.  I would guess, based on previous examples where you've used
this same class and related collections, that this isn't the case for
you.  But if it were, you could look at the item's position rather than
tracking a separate index.

But no, other than that, the enumeration doesn't have a built-in way of
returning the current position within the collection.  You'd have to
maintain it yourself somehow.

Pete
tshad replied on 02-Mar-08 05:03 PM
Which was why I was asking if that was the best way.  Which was to use a
counter - which works fine.

Actually, doing it outside would be fine as well, I also agre about the
separate method (which is actually how I do it).  But I don't see how that
makes it less awkward.

In my code (with the separate method) I would do something like:

int ktr = 0;
foreach (Racecar racecar in RaceCarCollection)
{
ktr++;
// Do something...........

if(ktr == RaceCarCollection.Count)
{
// Do last row stuff here...........
}
ProcessItem(racecar)
}

In one case, I would put the ProcessItem in 2 places inside of the loop as I
only process when something about the racecar object changes (the car for
example where I may have the car sorted by type - ford, chevy, mecedes,
ferrari etc).  The code then would look something like:

int ktr = 0;
string oldCar = null;

foreach (Racecar racecar in RaceCarCollection)
{
ktr++;
if (oldCar is null)
{
// initialize some variables the first time in
oldCar = racecar.Car;
}
// Do something...........
if (oldCar != racecar.Car)
{
// handle processing all the data for this car
ProcessItem(oldCar)
}

// Handle accumulation of data here for the new car

if(ktr == RaceCarCollection.Count)
{
// Do last row stuff here...........

ProcessItem(racecar.Car)
}
}

I think you may have misunderstood what I was trying to do.

I wasn't trying to process the data only when I have the last item.  I just
needed to make sure that the last one gets processed somewhere (either after
the foreach as you mentioned or inside the loop).

Also, unless I am mistaken, your example would not process the 1st item but
would process all the ones after it, including the last one.


That's true.

Thanks,

Tom
Peter Duniho replied on 02-Mar-08 06:27 PM
Well, I didn't say that putting the code in a separate method makes the
code less awkward.  I just think it's less of a problem to duplicate the
a single line, and one that doesn't itself implement any specific behavior.

Even with calling the processing in a method rather than copying and
pasting all of the processing code, the arrangement I suggested is still
awkward.  It just doesn't also have the problem of having a large block of
copy-and-pasted code.


Perhaps.  I think that I did understand the original question as stated
though.  And your subject line reinforces that understanding.


Judging from the responses, I don't think there's a single person who
replied who understood that you actually have intermediate accumulation
results.  Your subject line for this thread is very misleading if that's
the case.


Well, that's a good example of why I called it awkward.  It does indeed
process every single item.  But because of the awkward way it's written,
that's not immediately obvious to everyone who reads the code (for
example, you).

Code should be immediately obvious in its intent and operation as much as
possible.  The fact that the code I posted isn't immediately obvious is
directly connected to why I consider it awkward.  I'd only choose to write
the code like that if there was an important, significant advantage to
doing so that outweighed the relative lack of transparency with respect to
what the code actually does.

Pete
Ben Voigt [C++ MVP] replied on 03-Mar-08 07:09 PM
More likely, he wants something *not done* for the last item, for example
inserting a comma to separate items.  That something may or may not be easy
to undo.
tshad replied on 03-Mar-08 08:20 PM
Almost.

Actually, as Peter surmised, many have misunderstood the problem ( which I
am sure I didn't state correctly).

What I was looking for was a way to tell when I actually hit the last row -
to make sure it got processed.

I was going through the loop and if something change, such as a new car
style, then I processed what I had accumulated at that point.

Since I don't process until the car type changes, I don't want to process
the current row yet - just what I had accumulated up to that point.

I then now start a new accumulation with the current row.

But if the current row happens to be the last row - I want to process what I
have at that point.

I could either do it after I leave the foreach loop or process it inside the
loop, which was what I wanted to do.  So I need to do a check to see if this
was the last loop - if so process it.  Or do something special before
processing.

Thanks,

Tom
James Austin replied to tshad on 12-Apr-11 07:43 AM
I no this is an old post but...



My prefered route is:



foreach (var item in someCollection)

{

if (item != someCollection.Last)

{

// nothing happens here on last object

}

}



Requires System.Linq though.
James Austin replied to James Austin on 12-Apr-11 07:45 AM
PS.  I can't spell 'know'