我刚看了.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;
}
}
}
假设我有一个IEnumerable<T>
,其中包含1000个元素(基础类型为List<T>
)。如果我正在做list.Skip(990)会怎么样。拿(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;
}
那是什么原因?
答案 0 :(得分:13)
在Jon Skeet的优秀教程重新实施Linq中,他(简要地)讨论了这个问题:
虽然大多数这些操作都不能明智地优化,但它 在源实现IList时优化Skip是有意义的。 我们可以跳过跳过,可以这么说,直接去 适当的指数。这不会发现源的情况 在迭代之间进行修改,这可能是其中一个原因 据我所知,在框架中实现。
这似乎是推迟优化的合理理由,但我同意,对于特定情况,如果您可以保证您的来源不会/不会被修改,那么进行优化可能是值得的。
答案 1 :(得分:2)
正如ledbutter所提到的,当Jon Skeet reimplemented LINQ时,他提到像Skip
这样的优化不会发现在迭代之间修改源的情况“。您可以将代码更改为以下内容,以便检查该情况。它通过在集合的枚举器上调用MoveNext()
来实现,即使它不使用e.Current
,因此如果集合发生更改,该方法将抛出。
当然,这消除了优化的一个重要部分:枚举器需要被创建,部分步进和处理,但它仍然有一个好处,你不需要毫无意义地逐步完成第一个{{1对象。如果您的count
无效,可能会让您感到困惑,因为它指的是e.Current
而不是list[i - count]
。
list[i]
答案 2 :(得分:1)
我认为他们想要在另一个线程中同时修改底层集合时抛出InvalidOperationException
“集合被修改...”。你的版本没有这样做。它会产生可怕的结果。
这是MSFT在所有集合中贯穿整个.Net框架的标准做法,这些集合不是线程安全的(虽然有些是特殊的)。