【发布时间】:2013-11-28 22:59:40
【问题描述】:
刚刚看了.NET Framework的Skip/Take扩展方法的源码(IEnumerable<T>类型),发现内部实现是用GetEnumerator方法:
// .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;
}
}
}
假设我有一个包含 1000 个元素的 IEnumerable<T>(基础类型是 List<T>)。如果我在做 list.Skip(990).Take(10) 会发生什么?它会在取最后 10 个元素之前迭代 990 个第一个元素吗? (这就是我的理解)。如果是,那我不明白微软为什么没有像这样实现Skip 方法:
// 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;
}
}
}
}
事实上,他们就是为 Count 方法做的...
// .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;
}
那是什么原因呢?
【问题讨论】:
-
我发现最好假设这些方法从未优化过。即使对于 Count(),它也会针对
ICollection<>进行优化,但不会针对IReadOnlyCollection<>进行优化。如果需要优化,请自己编写。 -
因为他们从不费心添加优化?如果您发现它有帮助,我认为您自己这样做没有任何问题。但请注意,
myList.Select(..).Skip(100)比myList.Skip(100).Select(..)慢,即使它们在功能上相同。 -
另请注意,在 Linq-To-SQL 和 EF 中,
Skip和Take被下推到 SQL 查询中,因此它不会遍历前面的项目。 (SQL 可能通过表/索引扫描,但 Linq 不会) -
在这种情况下,您在
IQueryable<T>(而不是IEnumerable<T>)上调用Skip/Take方法,它具有不同的实现...
标签: c# performance linq ienumerable skip-take