在IList上调用.Last()会迭代整个列表吗?

时间:2011-01-28 21:27:49

标签: c#

如果在.Last()上调用了IList扩展方法吗?我只是想知道这些之间是否存在显着的性能差异:

IList<int> numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

int lastNumber1 = numbers.Last();
int lastNumber2 = numbers[numbers.Count-1];

Intuition告诉我第一个选择是O(n),但第二个是O(1)。 .Last()“智能”足以尝试将其投射到IList吗?

5 个答案:

答案 0 :(得分:19)

可能不是,因为它可以做list[list.count-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];
        }
    }
    ...
}

答案 1 :(得分:6)

这是未记录的优化,但Enumerable.Last的无谓词重载确实会直接跳到最后。

请注意,使用谓词的重载不仅仅是从最后开始,正如您所期望的那样向前工作 - 它从一开始就向前发展。我相信这是为了避免在谓词可能抛出异常(或引起其他副作用)时出现不一致。

有关详细信息,请参阅我的blog post about implementing First/Last/Single etc,以及在Single / SingleOrDefault的重载之间存在 的不一致。

答案 2 :(得分:3)

反射器:

public static TSource Last<TSource>(this IEnumerable<TSource> source)
{
    ...
    if (list != null)
    {
        int count = list.Count;
        if (count > 0)
        {
            return list[count - 1];
        }
    }
    else
    {
        using (IEnumerator<TSource> enumerator = source.GetEnumerator())
        {
            ...
        }
    }
    throw Error.NoElements();
}

答案 3 :(得分:1)

答案:是的。

这是一个很好的方法来找出:

class MyList<T> : IList<T> { 
    private readonly List<T> list = new List<T>();
    public T this[int index] {
        get {
            Console.WriteLine("Inside indexer!");
            return list[index];
        }
        set {
            list[index] = value;
        }
    }

    public void Add(T item) {
        this.list.Add(item);
    }

    public int Count {
        get {
            Console.WriteLine("Inside Count!");
            return this.list.Count;
        }
    }

    // all other IList<T> interface members throw NotImplementedException
}

然后:

MyList<int> list = new MyList<int>();
list.Add(1);
list.Add(2);
Console.WriteLine(list.Last());

输出:

Inside Count!
Inside indexer!
2

如果您尝试这样做:

Console.WriteLine(list.Last(n => n % 2 == 0));

然后你在GetEnumerator中得到一个异常,表明它正试图走列表。如果我们通过

实施GetEnumerator
public IEnumerator<T> GetEnumerator() {
    Console.WriteLine("Inside GetEnumerator");
    return this.list.GetEnumerator();
}

再试一次,我们看到

Inside GetEnumerator!
2

在控制台上显示索引器从未使用过。

答案 4 :(得分:0)

原创海报是在谈论界面,而不是实施。

所以它取决于所讨论的IList/Ilist<T>背后的底层实现。您不了解其索引器的实现方式。我相信框架的List<T>有一个利用数组的具体实现,因此可以直接查找,但如果你只有IList<T>的引用,那么这不是任何给定的。 / p>