Właśnie obejrzałem kod źródłowy metod rozszerzeń Skip
/Take
systemu .NET Framework (w typie IEnumerable<T>
) i okazało się, że wewnętrzna implementacja działa z Sposób GetEnumerator
:Wydajność pomijania (i podobnych funkcji, takich jak Take)
// .NET framework
public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int count)
{
if (source == null) throw Error.ArgumentNull("source");
return SkipIterator<TSource>(source, count);
}
static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
while (count > 0 && e.MoveNext()) count--;
if (count <= 0)
{
while (e.MoveNext()) yield return e.Current;
}
}
}
Załóżmy, że mieć IEnumerable<T>
z 1000 elementów (typ bazową jest List<T>
). Co się stanie, jeśli robię list.Skip (990) .Take (10)? Czy będzie to powtórzyć po 990 pierwszych elementach przed wzięciem ostatniej dziesiątki? (tak to rozumiem). Jeśli tak, to nie rozumiem, dlaczego Microsoft nie dostosował metodę Skip
takiego:
// Not tested... just to show the idea
public static IEnumerable<T> Skip<T>(this IEnumerable<T> source, int count)
{
if (source is IList<T>)
{
IList<T> list = (IList<T>)source;
for (int i = count; i < list.Count; i++)
{
yield return list[i];
}
}
else if (source is IList)
{
IList list = (IList)source;
for (int i = count; i < list.Count; i++)
{
yield return (T)list[i];
}
}
else
{
// .NET framework
using (IEnumerator<T> e = source.GetEnumerator())
{
while (count > 0 && e.MoveNext()) count--;
if (count <= 0)
{
while (e.MoveNext()) yield return e.Current;
}
}
}
}
W rzeczywistości, zrobili to dla metody Count
na przykład ...
// .NET Framework...
public static int Count<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
ICollection<TSource> collectionoft = source as ICollection<TSource>;
if (collectionoft != null) return collectionoft.Count;
ICollection collection = source as ICollection;
if (collection != null) return collection.Count;
int count = 0;
using (IEnumerator<TSource> e = source.GetEnumerator())
{
checked
{
while (e.MoveNext()) count++;
}
}
return count;
}
Jaki jest tego powód?
Odkryłem, że zawsze najlepiej jest założyć, że metody te nigdy nie zostały zoptymalizowane.Nawet dla Count() optymalizuje dla 'ICollection <>', ale nie 'IReadOnlyCollection <>'. Jeśli potrzebujesz go zoptymalizować, napisz własny. – RobSiklos
Ponieważ nigdy nie przejmowali się tą optymalizacją? Nie widzę żadnych problemów z robieniem tego samemu, jeśli okaże się, że to pomaga. Ale zauważ, że wtedy 'myList.Select (..). Pomiń (100)' jest wolniejszy niż 'myList.Skip (100). Wybierz (..)', mimo że funkcjonalnie są takie same. –
Należy również zauważyć, że w Linq-SQL i EF 'Skip' i' Take' są przesyłane do zapytania SQL, więc nie dokonuje iteracji po wcześniejszych elementach. (_SQL_ może przez skanowanie tabeli/indeksu, ale Linq nie) –