tags:

views:

200

answers:

5

I dont understand how current can be null and last can be an object while last being a LINQ function. 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 do linq Last() work?

var.GetEnumerator().Current
var.Last()
+5  A: 

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

Jon Skeet
Also, doesn't `collection.GetEnumerator().Current` return "the object before the first element of the collection"? Isn't part of the contract that you need to call MoveNext at least once before Current has a defined value?
Lasse V. Karlsen
@Lasse: Yes, although IIRC the iterator blocks generated by C# ignore this :(
Jon Skeet
`MoveNext()` *is* called before `Current`, in the `if` block.
Jason
+12  A: 

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();
}
Chris Schmich
+2  A: 

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.
Rubys
+1  A: 

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

Dan Puzey
The result is undefined, typically null though, but undefined nonetheless.
Lasse V. Karlsen
+1  A: 

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.

Oliver
This explains what was wrong with my thought process. Thanks!
acidzombie24