C#.First()vs

有兴趣,方法有什么不同。
所以,我创建了两个片段。

Snippet A List a = new List(); a.Add(4); a.Add(6); int b = a.First(); 

 Snippet B List a = new List(); a.Add(4); a.Add(6); int b = a[0]; 

在IL,我们相信,所以

 Snippet A IL IL_0000: nop IL_0001: newobj System.Collections.Generic.List..ctor IL_0006: stloc.0 // a IL_0007: ldloc.0 // a IL_0008: ldc.i4.4 IL_0009: callvirt System.Collections.Generic.List.Add IL_000E: nop IL_000F: ldloc.0 // a IL_0010: ldc.i4.6 IL_0011: callvirt System.Collections.Generic.List.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..ctor IL_0006: stloc.0 // a IL_0007: ldloc.0 // a IL_0008: ldc.i4.4 IL_0009: callvirt System.Collections.Generic.List.Add IL_000E: nop IL_000F: ldloc.0 // a IL_0010: ldc.i4.6 IL_0011: callvirt System.Collections.Generic.List.Add IL_0016: nop IL_0017: ldloc.0 // a IL_0018: ldc.i4.0 IL_0019: callvirt System.Collections.Generic.List.get_Item IL_001E: stloc.1 // b IL_001F: ret 

Snippet B产生了一个命令更多的IL,但最终接近哪个更快?

你可以自己检查一下:

  static void Main() { List resultsFirst = new List(); List resultsIndex = new List(); Stopwatch s = new Stopwatch(); for (int z = 0; z < 100; z++) { List[] lists = new List[10000]; int temp = 0; for (int i = 0; i < lists.Length; i++) lists[i] = new List() { 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

PS

方法First的源代码是:

 public static TSource First(this IEnumerable source) { IList list = source as IList; if (list != null) { if (list.Count > 0) { return list[0]; } } else { using (IEnumerator enumerator = source.GetEnumerator()) { if (enumerator.MoveNext()) { return enumerator.Current; } } } } 

source as IList的转换操作source as IList或创建Enumerator对象很可能是First()相当慢的原因。

Enumerable.First方法定义为

 public static TSource First(this IEnumerable source) { if (source == null) throw Error.ArgumentNull("source"); IList list = source as IList; if (list != null) { if (list.Count > 0) return list[0]; } else { using (IEnumerator e = source.GetEnumerator()) { if (e.MoveNext()) return e.Current; } } throw Error.NoElements(); } 

因此对于List它在空检查和强制转换后最终使用索引器。 看起来并不多,但是当我测试性能时, First比索引器慢10倍( for循环,10 000 000次迭代,发布版本:First – 100 ms,indexer – 10 ms)。

通常, 具体的类/接口方法应该优于通用实现,因为,后者是通用的 ,并且数据结构应该考虑其特定 。 例如,链表不应该提供索引器,因为它无法有效实现。 理想情况下,每个数据结构都会定义一个自己的方法,它具有与相应的通用扩展方法相同的签名,因为它可以提供更好的实现,编译器将正确处理它。 这可以被视为专业化 ,遗憾的是,在C ++模板中不能很好地支持它。 Enumerable.First的实现是“解决方法”而不是解决方案的一个很好的例子 – 它为特定的BCL接口进行优化,但是无法处理可以提供相同信息的自定义数据结构(如链接列表)。使用通用实现。 对于Enumerable.Last情况更糟。

要恢复,如果您针对特定的类/接口进行编程,请尽可能使用他们的方法。 如果您是针对标准通用接口进行编程,那么您无论如何都没有其他选项(除了定义影响标准接口的扩展方法,但这通常会导致冲突)。

如果你问的是渐近复杂性,两种方法都是O(1) ,使用它们中的任何一种。

如果你问的是真正的速度,那就没有答案,因为它可能因版本而异,从一台机器到另一台机器。 你生成的IL对于任何其他版本的.NET都不一样。

通过选择其中一种方法来尝试优化代码显然是过早的优化。

在LINQPad 5中测试,代码如下:

 var sw = Stopwatch.StartNew(); for(int i = 0; i < 1000000000; i++) { List a = new List(); 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] 。 谢谢。