user34537
user34537

Reputation:

How does linq Last() determine the last item?

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

Answers (5)

Oliver
Oliver

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

Dan Puzey
Dan Puzey

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

Rubys
Rubys

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

Chris Schmich
Chris Schmich

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

Jon Skeet
Jon Skeet

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

Related Questions