关键是在实施
Where()
在那里
IEnumerable
到
List<T>
如果可以的话。注意铸件的位置
WhereListIterator
(这是通过反射获得的.Net源代码):
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
if (source is List<TSource>) return new WhereListIterator<TSource>((List<TSource>)source, predicate);
return new WhereEnumerableIterator<TSource>(source, predicate);
}
我已经通过复制(并在可能的情况下简化).Net实现来验证了这一点。
重要的是,我实现了两个版本的
Count()
-一个叫
TestCount()
我使用的地方
IEnumerable<T>
,还有一个叫
TestListCount()
我把可枚举的
列表<T>
在清点物品之前。
这与我们看到的
其中()
运算符(如上所示)也强制转换为
列表<T>
如果可以的话。
(应在没有附加调试器的发布版本中尝试此操作。)
这表明它使用起来更快
foreach
迭代
列表<T>
与通过
IEnumerable<T>
.
首先,这里是完整的测试代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Demo
{
public class Group
{
public string Name
{
get;
set;
}
}
internal static class Program
{
static void Main()
{
int dummy = 0;
List<Group> groups = new List<Group>();
for (int i = 0; i < 10000; i++)
{
var group = new Group();
group.Name = i + "asdasdasd";
groups.Add(group);
}
Stopwatch stopwatch = new Stopwatch();
for (int outer = 0; outer < 4; ++outer)
{
stopwatch.Restart();
foreach (var group in groups)
dummy += TestWhere(groups, x => x.Name == group.Name).Count();
Console.WriteLine("Using TestWhere(): " + stopwatch.ElapsedMilliseconds);
stopwatch.Restart();
foreach (var group in groups)
dummy += TestCount(groups, x => x.Name == group.Name);
Console.WriteLine("Using TestCount(): " + stopwatch.ElapsedMilliseconds);
stopwatch.Restart();
foreach (var group in groups)
dummy += TestListCount(groups, x => x.Name == group.Name);
Console.WriteLine("Using TestListCount(): " + stopwatch.ElapsedMilliseconds);
}
Console.WriteLine("Total = " + dummy);
}
public static int TestCount<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
int count = 0;
foreach (TSource element in source)
{
if (predicate(element))
count++;
}
return count;
}
public static int TestListCount<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
return testListCount((List<TSource>) source, predicate);
}
private static int testListCount<TSource>(List<TSource> source, Func<TSource, bool> predicate)
{
int count = 0;
foreach (TSource element in source)
{
if (predicate(element))
count++;
}
return count;
}
public static IEnumerable<TSource> TestWhere<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
return new WhereListIterator<TSource>((List<TSource>)source, predicate);
}
}
class WhereListIterator<TSource>: Iterator<TSource>
{
readonly Func<TSource, bool> predicate;
List<TSource>.Enumerator enumerator;
public WhereListIterator(List<TSource> source, Func<TSource, bool> predicate)
{
this.predicate = predicate;
this.enumerator = source.GetEnumerator();
}
public override bool MoveNext()
{
while (enumerator.MoveNext())
{
TSource item = enumerator.Current;
if (predicate(item))
{
current = item;
return true;
}
}
Dispose();
return false;
}
}
abstract class Iterator<TSource>: IEnumerable<TSource>, IEnumerator<TSource>
{
internal TSource current;
public TSource Current
{
get
{
return current;
}
}
public virtual void Dispose()
{
current = default(TSource);
}
public IEnumerator<TSource> GetEnumerator()
{
return this;
}
public abstract bool MoveNext();
object IEnumerator.Current
{
get
{
return Current;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
void IEnumerator.Reset()
{
throw new NotImplementedException();
}
}
}
下面是为两个关键方法生成的IL,
TestCount():
和
testListCount()
请记住,它们之间的唯一区别是
测试计数()
正在使用
IEnumerable<T>
和
测试列表计数()
正在使用相同的可枚举值,但强制转换为其基础
列表<T>
类型:
TestCount():
.method public hidebysig static int32 TestCount<TSource>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource> source, class [mscorlib]System.Func`2<!!TSource, bool> predicate) cil managed
{
.maxstack 8
.locals init (
[0] int32 count,
[1] !!TSource element,
[2] class [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource> CS$5$0000)
L_0000: ldc.i4.0
L_0001: stloc.0
L_0002: ldarg.0
L_0003: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource>::GetEnumerator()
L_0008: stloc.2
L_0009: br L_0025
L_000e: ldloc.2
L_000f: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource>::get_Current()
L_0014: stloc.1
L_0015: ldarg.1
L_0016: ldloc.1
L_0017: callvirt instance !1 [mscorlib]System.Func`2<!!TSource, bool>::Invoke(!0)
L_001c: brfalse L_0025
L_0021: ldloc.0
L_0022: ldc.i4.1
L_0023: add.ovf
L_0024: stloc.0
L_0025: ldloc.2
L_0026: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_002b: brtrue.s L_000e
L_002d: leave L_003f
L_0032: ldloc.2
L_0033: brfalse L_003e
L_0038: ldloc.2
L_0039: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_003e: endfinally
L_003f: ldloc.0
L_0040: ret
.try L_0009 to L_0032 finally handler L_0032 to L_003f
}
testListCount():
.method private hidebysig static int32 testListCount<TSource>(class [mscorlib]System.Collections.Generic.List`1<!!TSource> source, class [mscorlib]System.Func`2<!!TSource, bool> predicate) cil managed
{
.maxstack 8
.locals init (
[0] int32 count,
[1] !!TSource element,
[2] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource> CS$5$0000)
L_0000: ldc.i4.0
L_0001: stloc.0
L_0002: ldarg.0
L_0003: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> [mscorlib]System.Collections.Generic.List`1<!!TSource>::GetEnumerator()
L_0008: stloc.2
L_0009: br L_0026
L_000e: ldloca.s CS$5$0000
L_0010: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource>::get_Current()
L_0015: stloc.1
L_0016: ldarg.1
L_0017: ldloc.1
L_0018: callvirt instance !1 [mscorlib]System.Func`2<!!TSource, bool>::Invoke(!0)
L_001d: brfalse L_0026
L_0022: ldloc.0
L_0023: ldc.i4.1
L_0024: add.ovf
L_0025: stloc.0
L_0026: ldloca.s CS$5$0000
L_0028: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource>::MoveNext()
L_002d: brtrue.s L_000e
L_002f: leave L_0042
L_0034: ldloca.s CS$5$0000
L_0036: constrained [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource>
L_003c: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0041: endfinally
L_0042: ldloc.0
L_0043: ret
.try L_0009 to L_0034 finally handler L_0034 to L_0042
}
我认为这里的重要台词是
IEnumerator::GetCurrent()
和
IEnumerator::MoveNext()
.
第一种情况是:
callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource>::get_Current()
callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
第二种情况是:
call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource>::get_Current()
call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<!!TSource>::MoveNext()
重要的是,在第二种情况下,正在进行一个非虚拟呼叫——如果它在一个循环中(当然是这样),这可能比虚拟呼叫快得多。