List <t>的Last()扩展方法的性能是什么?</t>

时间:2009-09-04 08:08:58

标签: c# .net linq extension-methods

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

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

C ++中的STL通过迭代器和其他东西的整个“继承树”来意识到这一点。

5 个答案:

答案 0 :(得分:39)

我刚刚使用Reference Source查看Last的代码,它首先检查它是否是IList<T>并执行相应的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();
}

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

答案 1 :(得分:22)

您可以将{Last List<T>与Last一起使用,而无需担心:)

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

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

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

答案 2 :(得分:7)

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

请注意,您发送的绝大多数内容都会实现IList<T>

List<int> 
int[] 

依此类推...全部实施IList<T>

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

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();
        }


    }
}

输出:

Raw performance Time Elapsed 1 ms
With Last Time Elapsed 31 ms

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

答案 3 :(得分:2)

对于List<T>,它是O(1),但对于其他可枚举,它可能是O(N)。

答案 4 :(得分:0)

简短回答

O(1)。

<强>解释

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

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

列表的

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