我最近想知道为什么Enumerable
- {@ 1}},Skip
或SkipWhile
等TakeWhile
- 扩展方法未进行优化,以便使用for
- 循环而不是枚举所有元素。
ElementAt
等其他方法首先检查序列是否可以转换为IList<T>
,然后使用索引器更快地找到元素。与First
,Last
,Count
等类似,他们首先检查IEnumerable<T>
是否可以转换为支持更快查找的集合。
为什么Skip
方法没有以这种方式优化?
以下是我从ILSpy
(。NET 4)获得的内容,SkipIterator
用于所有跳过方法,但类似的代码用于TakeWhile
:
// System.Linq.Enumerable
private static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (count > 0 && enumerator.MoveNext())
{
count--;
}
if (count <= 0)
{
while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
}
yield break;
}
正如您所看到的,如果可能,它甚至不会尝试使用for
- 循环。
为什么代码与此不相似?
public static IEnumerable<TSource> MySkip<TSource>(this IEnumerable<TSource> source, int count)
{
// check if it's a list or array to use a for-loop
IList<TSource> listT = source as IList<TSource>;
if (listT != null)
{
if (listT.Count <= count) yield break;
for(int i = count; i < listT.Count; i++)
yield return listT[i];
}
// no list or array, fallback to .NET method
foreach (TSource t in source.Skip(count))
yield return t;
}
我还针对默认的Skip
测试了这个扩展方法,这里是无意义的查询,它检查列表中有多少元素有一个跟随者(总是count-1),但它应该做的工作:< / p>
Random rnd = new Random();
var randomNumbers = Enumerable.Range(1, 100000)
.Select(i => new { Number = rnd.Next(100) })
.ToList();
Stopwatch sw1 = new Stopwatch();
sw1.Start();
int countAllButLast = randomNumbers
.TakeWhile((s, i) => randomNumbers.Skip(i).Any())
.Count();
sw1.Stop();
Console.WriteLine("Time for default Skip with foreach: {0} Result: {1}", sw1.Elapsed, countAllButLast);
Stopwatch sw2 = new Stopwatch();
sw2.Start();
countAllButLast = randomNumbers
.TakeWhile((s, i) => randomNumbers.MySkip(i).Any())
.Count();
sw2.Stop();
Console.WriteLine("Time for MySkip with for-loop: {0} Result: {1}", sw2.Elapsed, countAllButLast);
性能差异巨大:
Time for default Skip with foreach: 00:01:16.5630092 Result: 99999
Time for MySkip with for-loop: 00:00:00.0334746 Result: 99999