Какова эффективность метода расширения Last() для List <T>?

Мне очень нравится Last() и будет использовать его все время для List<T> s. Но поскольку он определен для IEnumerable<T>, я предполагаю, что он перечисляет сначала перечисление - это должно быть O (n), а не O (1) для прямой индексации последнего элемента a List<T>.

Знают ли стандартные методы расширения (Linq) об этом?

STL в С++ знает об этом в силу целого "дерева наследования" для итераторов и еще чего-то.

Ответ 1

Я использовал рефлектор для просмотра кода для Last и он проверяет, является ли он IList<T> первым и выполняет соответствующий вызов O (1):


EDIT:

По совету моего адвоката я удалил фрагмент кода из рефлектора. Если вы хотите увидеть код для себя, я предлагаю вам либо получить Reflector, либо сделать это самостоятельно (что каждый должен иметь уже), или более просто посмотреть в истории изменений этого сообщения (поскольку я ничего не могу с этим поделать)

(Или, действительно, посмотрите на ответ Марка, у которого явно нет такой же правовой команды, которая защитила его)


Итак, у вас есть легкие накладные расходы, но не огромные издержки перечисления.

Ответ 2

Вы можете просто использовать Last с List<T>, не беспокоясь:)

Enumerable.Last пытается сбрасывать экземпляр IEnumerable<T> на IList<T>. Если это возможно, он использует свойство indexer и Count.

Вот часть реализации, поскольку Reflector видит это:

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

Ответ 3

Он содержит оптимизацию для всего, что реализует 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 раз медленнее и поддерживает этот профиль производительности с любым списком длины, который у вас есть, что ничто в большой схеме вещей.

Ответ 4

Для List<T> это O (1), но для других перечислений это может быть O (N).

Ответ 5

Короткий ответ:

О (1).

Объяснение

Очевидно, что Last() для List использует метод расширения Count().

Count() проверяет тип коллекции во время выполнения и использует свойство Count, если оно доступно.

Свойство Count для списка имеет сложность O (1), а также метод расширения Last().