Reputation:
I don't understand how Current can be null and the LINQ function Last() can return an object. I thought Last uses GetEnumerator and keeps going until current == null and returns the object. However as you can see the first GetEnumerator().Current is null and last somehow returns an object.
How does linq Last() work?
items.GetEnumerator().Current
items.Last()
Upvotes: 11
Views: 9443
Reputation: 45109
If you take a look at IEnumerator Interface into the Remarks section, they will state the following:
Initially, the enumerator is positioned before the first element in the collection. At this position, Current is undefined. Therefore, you must call MoveNext to advance the enumerator to the first element of the collection before reading the value of Current.
So you have to call MoveNext()
once to get the first item. Otherwise you'll get just nothing.
Upvotes: 1
Reputation: 34218
Remember that calling GetEnumerator
doesn't typically/necessarily return the same Enumerator
each time. Also, since thing.GetEnumerator()
returns a new Enumerator
which will start uninitialized (you haven't called MoveNext()
yet), thing.GetEnumerator().Current
will always be null by definition.
(I think...)
Upvotes: 1
Reputation: 3207
The very first value of an enumerator, before any MoveNext(), is, in the case of an array, the item at index -1.
You have to do MoveNext once to enter the actual collection.
This is done so that the constructor of the enumerator doesn't do much work, and so that these constructs are valid:
while (enumerator.MoveNext()) { // Do Stuff } if(enumerator.MoveNext()) { // Do Stuff } // This is identical to Linq's .Any(), essentially.
Upvotes: 2
Reputation: 29494
From using Reflector on System.Core.dll
:
public static TSource Last<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
int count = list.Count;
if (count > 0)
{
return list[count - 1];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
TSource current;
do
{
current = enumerator.Current;
}
while (enumerator.MoveNext());
return current;
}
}
}
throw Error.NoElements();
}
Upvotes: 22
Reputation: 1502376
Last()
will call GetEnumerator()
, then keep calling MoveNext()
/ Current
until MoveNext()
returns false, at which point it returns the last value of Current
retrieved. Nullity is not used as a terminator in sequences, generally.
So the implementation might be something like this:
public static T Last<T>(this IEnumerable<T> source)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
using (IEnumerator<T> iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
throw new InvalidOperationException("Empty sequence");
}
T value = iterator.Current;
while (iterator.MoveNext())
{
value = iterator.Current;
}
return value;
}
}
(This could be implemented with a foreach
loop, but the above shows the interaction more explicitly. This also ignores the possibility of accessing the last element directly.)
Upvotes: 8