List 的Last()扩展方法的性能如何?

我真的很喜欢Last()并且会一直使用List 。 但由于它似乎是为IEnumerable定义的,我想它首先枚举枚举 – 这应该是O(n)而不是O(1)直接索引List的最后一个元素。

标准(Linq)扩展方法是否意识到这一点?

C ++中的STL通过迭代器和诸如此类的整个“inheritance树”来意识到这一点。

我只是使用引用源查看Last的代码,它首先检查它是否是IList并执行相应的O(1)调用:

 public static TSource Last < TSource > (this IEnumerable < TSource > source) { if (source == null) throw Error.ArgumentNull("source"); IList < TSource > list = source as IList < TSource > ; if (list != null) { int count = list.Count; if (count > 0) return list[count - 1]; } else { using(IEnumerator < TSource > e = source.GetEnumerator()) { if (e.MoveNext()) { TSource result; do { result = e.Current; } while ( e . MoveNext ()); return result; } } } throw Error.NoElements(); } 

所以你有一个演员的轻微开销,但不是枚举的巨大开销。

你可以使用Last with List而不用担心:)

Enumerable.Last尝试将IEnumerable实例向下转换为IList 。 如果可以,它使用索引器和Count属性。

以下是Reflector看到的实现的一部分:

 IList list = source as IList; if (list != null) { int count = list.Count; if (count > 0) { return list[count - 1]; } } 

它包含对实现IList任何内容的优化,在这种情况下,它只是查找项目的长度为-1。

请记住,您将发送的绝大多数内容将实现IList

 List int[] 

等等……全部实现IList

对于那些无法查看代码进行确认的人,可以使用观察来确认:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; namespace ConsoleApplication4 { class Program { static void Profile(string description, int iterations, Action func) { // clean up GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); // warm up func(); var watch = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { func(); } watch.Stop(); Console.Write(description); Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds); } static void Main(string[] args) { int[] nums = Enumerable.Range(1, 1000000).ToArray(); int a; Profile("Raw performance", 100000, () => { a = nums[nums.Length - 1]; }); Profile("With Last", 100000, () => { a = nums.Last(); }); Console.ReadKey(); } } } 

输出:

原始性能时间经过1毫秒
上次经过31毫秒

所以它只有30倍的速度,并且保持了你所拥有的任何长度列表的性能配置文件,这在大的方案中没什么。

对于List它是O(1),但对于其他可枚举,它可以是O(N)。

简短回答

O(1)。

说明

很明显List的 Last()使用Count()扩展方法。

Count()在运行时检查集合的类型,并使用Count属性(如果可用)。

列表的Count属性具有O(1)复杂度,因此Last()扩展方法也是如此。