2010-05-20 18 views
9

No entiendo cómo la corriente puede ser nula y la última puede ser un objeto siendo la última una función LINQ. Pensé que el último usa GetEnumerator y continúa hasta current == null y devuelve el objeto. Sin embargo, como puede ver el primer GetEnumerator(). Current es nulo y el último de alguna forma devuelve un objeto.¿Cómo funciona linq Last()?

¿Cómo funciona linq Last()?

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

Estás confundiendo secuencias con sus enumeradores. Imagina un libro con páginas numeradas. Esa es una secuencia. Imagine un marcador, marcando una página en particular. Eso es un enumerador. Puede tener cientos de marcadores en un libro si lo desea, todos marcando diferentes lugares. Llamando a GetEnumerator.Current está pidiendo la página en la que está marcado un marcador cuando aún no lo has incluido en el libro; no hagas eso –

Respuesta

16

Desde el uso de Reflector en 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(); 
} 
7

Last() llamarán GetEnumerator(), a continuación, seguir llamando MoveNext()/Current hasta MoveNext() vuelve falsa, momento en el que se devuelve el último valor de Current recuperado. La nulidad no se usa como un terminador en secuencias, generalmente.

Así que la aplicación podría ser algo como esto:

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; 
    } 
} 

(Esto podría ser implementado con un bucle foreach, pero lo anterior muestra la interacción de manera más explícita Esto también ignora la posibilidad de acceder al último elemento directamente. .)

+1

Además, no 'collection.GetEnumerator(). Current' devuelve" el objeto antes del primer elemento de la colección "? ¿No es parte del contrato que necesita llamar a MoveNext al menos una vez antes de que Current tenga un valor definido? –

+1

@Lasse: Sí, aunque IIRC los bloques iteradores generados por C# ignoran esto :( –

+1

'MoveNext()' * * se * llama antes de 'Current', en el bloque' if'. – Jason

2

el vE primer valor de un enumerador, antes de cualquier MoveNext(), es, en el caso de una matriz, el elemento en el índice -1.
Tienes que hacer MoveNext una vez para ingresar a la colección real.
Esto se hace para que el constructor del empadronador no hace mucho trabajo, y para que estas construcciones son válidas:

while (enumerator.MoveNext()) { 
     // Do Stuff 
} 

if(enumerator.MoveNext()) { 
     // Do Stuff 
} // This is identical to Linq's .Any(), essentially. 
1

Recuerde que llamar GetEnumerator no suele/devuelve necesariamente la el mismo Enumerator cada vez. Además, dado que thing.GetEnumerator() devuelve un nuevo Enumerator que comenzará sin inicializar (aún no ha llamado a MoveNext()), thing.GetEnumerator().Currentserá siempre nulo por definición.

(creo ...)

+1

El resultado es indefinido, por lo general nulo, pero indefinido, sin embargo. –

1

Si se echa un vistazo a IEnumerator Interface en la sección de observaciones, se indicarán los siguientes datos:

Inicialmente, el empadronador es situado antes del primer elemento en la colección. En esta posición, Current is undefined. Por lo tanto, debe llamar a MoveNext para avanzar al enumerador al primer elemento de la colección antes de leer el valor de Actual.

Así que tiene que llamar al MoveNext() una vez para obtener el primer elemento. De lo contrario, no obtendrás nada.

+0

Esto explica lo que estaba mal con mi proceso de pensamiento. ¡Gracias! –