2010-05-20 3 views
9

Je ne comprends pas comment le courant peut être nul et le dernier peut être un objet tout en étant une fonction LINQ. Je pensais que Last utilise GetEnumerator et continue jusqu'à current == null et renvoie l'objet. Cependant, comme vous pouvez le voir le premier GetEnumerator(). Current est nul et dernier renvoie en quelque sorte un objet.Comment linq Last() fonctionne-t-il?

Comment linq Last() fonctionne-t-il?

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

Vous confondez des séquences avec leurs énumérateurs. Imaginez un livre avec des pages numérotées. C'est une séquence. Imaginez un signet, en marquant une page particulière. C'est un énumérateur. Vous pouvez avoir une centaine de signets dans un livre si vous le souhaitez, tout en marquant différents endroits. Appel de GetEnumerator.Current demande la page sur laquelle un signet est activé lorsque vous ne l'avez pas encore mis dans le livre; Ne fais pas ça. –

Répondre

16

de l'utilisation Reflector sur 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() appellera GetEnumerator(), puis continuer à appeler MoveNext()/Current jusqu'à ce que MoveNext() renvoie false, à quel point elle retourne la dernière valeur de Current récupéré. La nullité n'est généralement pas utilisée comme terminateur dans les séquences.

Ainsi, la mise en œuvre pourrait être quelque chose comme ceci:

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

(Cela pourrait être mis en œuvre avec une boucle foreach, mais qui précède montre l'interaction plus explicitement ignore également la possibilité d'accéder au dernier élément directement. .)

+1

En outre, 'collection.GetEnumerator(). Current' ne renvoie-t-il pas" l'objet avant le premier élément de la collection "? N'est-ce pas une partie du contrat que vous devez appeler MoveNext au moins une fois avant que Current ait une valeur définie? –

+1

@Lasse: Oui, bien que IIRC les blocs d'itérateur générés par C# ignorent ceci :( –

+1

'MoveNext()' * est * appelé avant 'Current', dans le bloc' if' – Jason

2

Le ve ry première valeur d'un énumérateur, avant tout MoveNext(), est, dans le cas d'un tableau, l'élément à l'index -1.
Vous devez faire MoveNext une fois pour entrer dans la collection actuelle.
Ceci est fait pour que le constructeur du recenseur ne fait pas beaucoup de travail, et que ces constructions sont valables:

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

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

Rappelez-vous que l'appel GetEnumerator ne retourne généralement pas/nécessairement même Enumerator chaque fois. En outre, puisque thing.GetEnumerator() renvoie un nouveau Enumerator qui commencera non initialisé (vous n'avez pas encore appelé MoveNext()), thing.GetEnumerator().Current sera toujours nul par définition.

(je pense ...)

+1

Le résultat est indéfini, généralement null, –

1

Si vous jetez un oeil à IEnumerator Interface dans la section Remarques, ils indiquent ce qui suit:

Dans un premier temps, le recenseur est placé avant la premier élément dans la collection. À cette position, le courant est indéfini. Par conséquent, vous devez appeler MoveNext pour avancer l'énumérateur vers le premier élément de la collection avant de lire la valeur de Current.

Vous devez donc appeler le MoveNext() une fois pour obtenir le premier article. Sinon, vous n'obtiendrez rien.

+0

Ceci explique ce qui n'allait pas dans mon processus de pensée. –

Questions connexes