Рассмотрите этот код:
int size = 100 * 1000 * 1000;
var emu = Enumerable.Range(0, size);
var arr = Enumerable.Range(0, size).ToArray();
когда я называю страуса эму. ElementAt (размер 10) и прибытие. ElementAt (размер 10) и мера время, прибытие намного быстрее (массив составляет 0,0002 с по сравнению с IEnumerable 0,59 с).
Насколько я понимаю метод расширения ElementAt () имеет подпись
public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index)
и так как 'источник' является IEnumerable, выполненная логика была бы подобна - настроенный против того, что я вижу, где к массиву получают доступ непосредственно.
Мог кто-то объяснять это :)
Это оптимизация, выполняемая на этапе выполнения. Хотя вызов не перегружен, он способен проверить (используя is
или as
), действительно ли источник является IList
. Если да, то он может перейти непосредственно к нужному элементу.
Это делают и другие вызовы - в частности, Count()
, который оптимизирован для ICollection
и (начиная с .NET 4) негенерического интерфейса ICollection
.
Одним из недостатков методов расширения является то, что все эти оптимизации должны выполняться самой реализацией - типы не могут переопределить что-либо, чтобы "отказаться" от оптимизации методов расширения. Это означает, что все оптимизации должны быть известны оригинальному реализатору :(
Вызов ElementAt
в IEnumerable
будет перебирать элементы до тех пор, пока не будет достигнут желаемый индекс. (Операция O (n))
Вызов ElementAt
в IList
(например, массив) будет использовать IList
индексатор, чтобы сразу получить желаемый индекс.(Операция O (1))