有兴趣,方法有什么不同。
所以,我创建了两个片段。
Snippet A
List<int> a = new List<int>();
a.Add(4);
a.Add(6);
int b = a.First();
和
Snippet B
List<int> a = new List<int>();
a.Add(4);
a.Add(6);
int b = a[0];
我们相信IL,所以
Snippet A IL
IL_0000: nop
IL_0001: newobj System.Collections.Generic.List<System.Int32>..ctor
IL_0006: stloc.0 // a
IL_0007: ldloc.0 // a
IL_0008: ldc.i4.4
IL_0009: callvirt System.Collections.Generic.List<System.Int32>.Add
IL_000E: nop
IL_000F: ldloc.0 // a
IL_0010: ldc.i4.6
IL_0011: callvirt System.Collections.Generic.List<System.Int32>.Add
IL_0016: nop
IL_0017: ldloc.0 // a
IL_0018: call System.Linq.Enumerable.First
IL_001D: stloc.1 // b
IL_001E: ret
和
Snippet B IL
IL_0000: nop
IL_0001: newobj System.Collections.Generic.List<System.Int32>..ctor
IL_0006: stloc.0 // a
IL_0007: ldloc.0 // a
IL_0008: ldc.i4.4
IL_0009: callvirt System.Collections.Generic.List<System.Int32>.Add
IL_000E: nop
IL_000F: ldloc.0 // a
IL_0010: ldc.i4.6
IL_0011: callvirt System.Collections.Generic.List<System.Int32>.Add
IL_0016: nop
IL_0017: ldloc.0 // a
IL_0018: ldc.i4.0
IL_0019: callvirt System.Collections.Generic.List<System.Int32>.get_Item
IL_001E: stloc.1 // b
IL_001F: ret
Snippet B产生了一个更多的IL命令,但最终哪个方法更快?
答案 0 :(得分:6)
你可以自己检查一下:
static void Main()
{
List<long> resultsFirst = new List<long>();
List<long> resultsIndex = new List<long>();
Stopwatch s = new Stopwatch();
for (int z = 0; z < 100; z++)
{
List<int>[] lists = new List<int>[10000];
int temp = 0;
for (int i = 0; i < lists.Length; i++)
lists[i] = new List<int>() { 4, 6 };
s.Restart();
for (int i = 0; i < lists.Length; i++)
temp = lists[i].First();
s.Stop();
resultsFirst.Add(s.ElapsedTicks);
s.Restart();
for (int i = 0; i < lists.Length; i++)
temp = lists[i][0];
s.Stop();
resultsIndex.Add(s.ElapsedTicks);
}
Console.WriteLine("LINQ First() : " + resultsFirst.Average());
Console.WriteLine(Environment.NewLine);
Console.WriteLine("By index : " + resultsIndex.Average());
Console.ReadKey();
}
发布模式下的输出:
LINQ First():367
按索引:84
调试模式下的输出:
LINQ First():401
按索引:177
P.S。
方法First的源代码是:
public static TSource First<TSource>(this IEnumerable<TSource> source)
{
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
if (list.Count > 0)
{
return list[0];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
return enumerator.Current;
}
}
}
}
转换操作source as IList<TSource>
或创建Enumerator对象很可能是First()
速度相当慢的原因。
答案 1 :(得分:4)
Enumerable.First
方法定义为
public static TSource First<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
IList<TSource> list = source as IList<TSource>;
if (list != null) {
if (list.Count > 0) return list[0];
}
else {
using (IEnumerator<TSource> e = source.GetEnumerator()) {
if (e.MoveNext()) return e.Current;
}
}
throw Error.NoElements();
}
因此对于List<T>
,它在空检查和强制转换后最终使用索引器。看起来并不多,但是当我测试性能时,First
比索引器(for
循环,10 000 000次迭代,释放构建:第一次 - 100 ms,索引器 - 10 ms)慢10倍。< / p>
答案 2 :(得分:2)
一般来说,具体类/接口方法应该优于泛型实现,因为,后者是泛型和数据结构应该考虑其细节。例如,链表不应该提供索引器,因为它无法有效实现。理想情况下,每个数据结构将定义一个自己的方法,它具有与相应的通用扩展方法相同的签名,因为它可以提供更好的实现,编译器将正确处理它。这可以被视为 specialization ,遗憾的是,在C ++模板中不能很好地支持它。 Enumerable.First
的实现是“解决方法”而不是解决方案的一个很好的例子 - 它针对特定的BCL接口进行优化,但是无法处理可以提供相同信息的自定义数据结构(如链接列表)比使用通用实现更好。对于Enumerable.Last
来说,情况更糟。
要恢复,如果您针对特定的类/接口进行编程,请尽可能使用他们的方法。如果您是针对标准通用接口进行编程,那么,您无其他选择(除了定义影响标准接口的扩展方法,但这通常会导致冲突)。
答案 3 :(得分:0)
如果您要询问渐近复杂度,两种方法都是O(1)
,请使用其中任何一种。
如果您要询问真正的速度,那么没有答案,因为它可能因版本而异,从一台机器到另一台机器。您生成的IL与任何其他版本的.NET不同。
通过选择其中一种方法来尝试优化代码显然是过早的优化。
答案 4 :(得分:0)
使用以下代码在LINQPad 5中测试:
var sw = Stopwatch.StartNew();
for(int i = 0; i < 1000000000; i++)
{
List<int> a = new List<int>();
a.Add(i);
a.Add(i+2);
int b = a.First();//[0] for B
}
sw.Stop();
Console.WriteLine(sw.ElapsedTicks);
.First()
给出了01:04.021和0:45.794的优化。
正如我所想,[0]
为我提供了优化和更好的代码0:44.288,0:27.968。
实际上,对我而言[0]
比.First()
更具可读性,而且通常我不需要他提供的支票。
因此,在大多数情况下,我会选择[0]
。
感谢。