Лучше ли вызывать ToList () или ToArray () в запросах LINQ?

Я часто сталкиваюсь со случаем, когда хочу проверить запрос именно там, где я его объявляю. Обычно это потому, что мне нужно повторить его несколько разand это дорого вычислять. Например:

string raw = "...";
var lines = (from l in raw.Split('\n')
             let ll = l.Trim()
             where !string.IsNullOrEmpty(ll)
             select ll).ToList();

Это отлично работает.But если я не собираюсь изменять результат, я мог бы такжеToArray() вместоToList().

Интересно однакоToArray() реализуется путем первого вызоваToList() и, следовательно, менее эффективно использовать память, чем просто вызовToList().

Я сумасшедший? Должен ли я просто позвонитьToArray() - безопасно и надежно знать, что память не будет распределяться дважды?

 steve19 февр. 2016 г., 22:08
Я не согласен с тем, чтоstackoverflow.com/questions/6750447/c-toarray-performance является дубликатом этого вопроса, хотя есть важные отношения. Как использование памяти (этот вопрос), так и производительность (другой вопрос) и являются интересными и нетривиальными соображениями. Они могут быть описаны отдельно, но оба должны учитывать решение о выборе одного над другим. Я не могу рекомендовать ни один из ответов на этот или другой вопрос как всеобъемлющий. Есть несколько ответов, которые, взятые вместе, дают довольно полное обсуждение того, как выбрать один над другим.
 David Hedlund09 июл. 2009 г., 21:39
Если вы когда-нибудь захотите узнать, что происходит за кулисами в .NET, я очень рекомендую.NET Reflector
 Gqqnbig09 июн. 2014 г., 21:05
@DavidHedlund я рекомендую.net source code.

Ответы на вопрос(15)

кольку вы не можете изменить размер массива, оба метода будут использовать какой-то механизм для сбора данных в растущей коллекции. (Ну, список сам по себе является растущей коллекцией.)

Список использует массив в качестве внутреннего хранилища и удваивает емкость при необходимости. Это означает, что в среднем 2/3 предметов было перераспределено, по крайней мере, один раз, половина из них перераспределена, по крайней мере, дважды, половина из них, по крайней мере, трижды, и так далее. Это означает, что каждый элемент был в среднем перераспределен в 1,3 раза, что не сильно увеличивает накладные расходы.

Помните также, что если вы собираете строки, сама коллекция содержит только ссылки на строки, сами строки не перераспределяются.

 Guffa02 авг. 2013 г., 17:09
@ JonofAllTrades: Нет, массив никогда не расширяется, управление памятью в .NET просто не делает этого. Если бы он был расширен на месте, не было бы необходимости перераспределять элементы.
 user56586902 авг. 2013 г., 17:43
Ах, я вижу: предметы, которые не были перераспределены, не должны были делать это, потому что они были в окончательном размещении. Все элементы, выделенные в предыдущих распределениях, перемещаются, но из-за логарифмического увеличения длины массива это вычисляемая доля. Спасибо за разъяснение
 user56586902 авг. 2013 г., 16:38
Это может быть невежественным вопросом, но разве логика 2/3, 1/3, 1/6, которую вы обрисовали, не предполагает, что массив List может быть расширен на месте? То есть в конце массива есть свободное место, так что существующее распределение не нужно перемещать?

ToList() обычно предпочтительнее, если вы используете его наIEnumerable<T> (из ORM, например). Если длина последовательности неизвестна в начале,ToArray() создает коллекцию динамической длины, такую как List, а затем преобразует ее в массив, что занимает дополнительное время.

 BrandonAGr09 янв. 2013 г., 00:18
В поисках ILSpy,Enumerable.ToArray() звонкиnew Buffer<TSource>(source).ToArray(). В конструкторе Buffer, если источник реализует ICollection, он вызывает source.CopyTo (items, 0), а затем .ToArray () возвращает массив внутренних элементов напрямую. Таким образом, в этом случае нет преобразования, которое требует дополнительного времени. Если источник не реализует ICollection, то ToArray приведет к копированию массива, чтобы обрезать лишние неиспользуемые места в конце массива, как описано выше в комментарии Скотта Риппи.
 doug6553620 окт. 2016 г., 01:07
@ FrankKrueger Чем отличается читаемость?
 Frank Krueger01 февр. 2010 г., 18:43
Я решил, что в этом случае удобочитаемость выше производительности. Теперь я использую ToList только тогда, когда ожидаю продолжения добавления элементов. Во всех других случаях (в большинстве случаев) я использую ToArray. Но спасибо за вклад!

Они оба отстой, когда создали с помощью linq. Они оба реализуют один и тот же код для изменить размер буфера при необходимости. ToArray внутренне использует класс для преобразованияIEnumerable<> для массива, выделив массив из 4 элементов. Если этого недостаточно, он удваивает размер, создавая новый массив, удваивая размер текущего и копируя в него текущий массив. В конце он выделяет новый массив количества ваших предметов. Если ваш запрос возвращает 129 элементов, то ToArray сделает 6 выделений и операций копирования памяти, чтобы создать массив из 256 элементов, а затем - еще один массив из 129, который нужно вернуть. так много для эффективности памяти.

ToList делает то же самое, но пропускает последнее распределение, так как вы можете добавлять элементы в будущем. List не заботится, создан ли он из запроса linq или создан вручную.

for создание List лучше с памятью, но хуже с процессором, так как list - универсальное решение, каждое действие требует проверки диапазона в дополнение к внутренней проверке диапазона .net для массивов.

Так что, если вы будете повторять свой набор результатов слишком много раз, то массивы хороши, поскольку это означает меньше проверок диапазона, чем списки, а компиляторы обычно оптимизируют массивы для последовательного доступа.

Распределение инициализации списка может быть лучше, если вы укажете параметр емкости при его создании. В этом случае он будет выделять массив только один раз, при условии, что вы знаете размер результата.ToList of linq не определяет перегрузку для ее предоставления, поэтому нам нужно создать наш метод расширения, который создает список с заданной емкостью, а затем используетList<>.AddRange.

Чтобы закончить этот ответ, я должен написать следующие предложения

В конце вы можете использовать ToArray или ToList, производительность не будет такой разной (см. Ответ @EMP). Вы используете C #. Если вам нужна производительность, не беспокойтесь о написании кода с высокой производительностью, а не о том, чтобы не писать код с плохой производительностью. Всегда предназначайтесь для x64 для высокопроизводительного кода. AFAIK, x64 JIT основан на компиляторе C ++ и выполняет некоторые забавные вещи, такие как оптимизация хвостовой рекурсии. С 4.5 вы также можете наслаждаться оптимизацией по профилю и многоядерным JIT. Наконец, вы можете использовать шаблон async / await для быстрой обработки.
 nawfal25 окт. 2013 г., 10:59
Хммм, в конце концов, именно там вы рисуете линию. Мне нравится текущая реализация. Тон вашего ответа заставил меня думать, что это была критика, а не где проблема:)
 nawfal22 окт. 2013 г., 11:35
Они оба сосут? У вас есть альтернативная идея, которая не требует избыточного выделения памяти?
 nawfal24 окт. 2013 г., 20:15
Создайте список 200? Чтоможет быт избегайте изменения размера, но я говорил об избыточной используемой памяти. Вы не можете помочь, потому что нет никаких предварительных знаний о том, какой размер может быть. Вы уже можете указать емкость в конструктореList<T>, но когда ты этого не делаешь или не можешь, ты ничего не можешь с этим поделать.
 edokan25 окт. 2013 г., 10:30
единственные избыточные данные в памяти - это содержимое массива, представляющего собой список указателей (в данном случае). один миллион 64-битных указателей занимает до 8 МБ памяти, что ничто по сравнению с одним миллионом объектов, на которые они указывают. 200 - это просто число, и оно может уменьшить количество вызовов с изменением размера максимум в 5 раз. и да, мы не можем с этим поделать. у нас нет лучших вариантов. У меня нет лучшего решения, но это не значит, что я не могу сказать, где проблема.
 edokan24 окт. 2013 г., 18:29
В контексте вопроса, да, они оба отстой, но из-за избыточного распределения и ничего больше. Чтобы уменьшить избыточное распределение, можно использовать связанные списки за счет памяти и скорости итерации. В конце концов, это то, что мы делаем, мы делаем компромиссы. Другая идея, если создать список с емкостью 200 (например), а затем загрузить элементы. Это также уменьшит избыточность, но массивы всегда быстрее, так что это еще один компромис

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

Если производительность имеет значение, вы должны также подумать о том, что будет быстрее работать. Реально, вы не будете звонитьToList илиToArray миллион раз, номожет быт работаю над полученным сбором миллион раз. В этом отношении[] лучше, так какList<> является[] с некоторыми накладными расходами. Смотрите эту тему для сравнения эффективности: Какой из них более эффективен: List <int> или int []

В своих собственных тестах я нашелToArray Быстрее. И я не уверен, насколько искажены были тесты. Разница в производительности настолько незначительна, что заметна, только если вы выполняете эти запросы в цикле миллионы раз.

 RobSiklos12 апр. 2013 г., 19:47
Да - если компилятор знает, что вы выполняете итерации по массиву (а не IEnumerable <>), он может значительно оптимизировать итерацию.

Несколько других (хороших) ответов были сосредоточены на микроскопических различиях в производительности.

Этот пост просто дополнение, чтобы упомянуть семантическая разница который существует междуIEnumerator<T> производится массивом T[]) по сравнению с возвращаемымList<T>.

Лучше всего проиллюстрировано на примере:

IList<int> source = Enumerable.Range(1, 10).ToArray();  // try changing to .ToList()

foreach (var x in source)
{
  if (x == 5)
    source[8] *= 100;
  Console.WriteLine(x);
}

Вышеприведенный код будет работать без исключения и выдаст результат:

1
2
3
4
5
6
7
8
900
10

Это показывает, чтоIEnumarator<int> возвращаетсяint[] не отслеживает, был ли массив изменен с момента создания перечислителя.

Обратите внимание, что я объявил локальную переменнуюsource какIList<int>. Таким образом, я убедился, что компилятор C # не оптимизируетforeach утверждение в нечто, эквивалентноеfor (var idx = 0; idx < source.Length; idx++) { /* ... */ } петля. Это то, что может сделать компилятор C #, если я используюvar source = ...; вместо. В моей текущей версии .NET Framework фактический перечислитель, используемый здесь, является непубличным ссылочным типомSystem.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32] но, конечно, это деталь реализации.

Теперь, если я переоденусь.ToArray() в.ToList(), Я получаю только:

1
2
3
4
5

сопровождаетсяSystem.InvalidOperationException взрывное высказывание:

Collection была изменена; операция перечисления может не выполняться.

Базовым перечислителем в этом случае является публичный изменяемый тип значенияSystem.Collections.Generic.List`1+Enumerator[System.Int32] (в штучной упаковке внутриIEnumerator<int> box в этом случае, потому что я используюIList<int>).

В заключение перечислитель, созданныйList<T> отслеживает, изменяется ли список во время перечисления, в то время как перечислитель создаетсяT[] не. Поэтому учитывайте эту разницу при выборе между.ToList() а также.ToArray().

Люди часто добавляют один Дополнительные .ToArray() или.ToList() чтобы обойти коллекцию, которая отслеживает, была ли она изменена в течение срока службы перечислителя.

(Если кто-то хочет знатька theList<> отслеживает, была ли изменена коллекция, есть личное поле_version в этом классе, который меняется каждый раз, когдаList<> обновляется.)

List<T> реализован в виде динамически изменяемого массива. Звонок либоToArray() (который использует внутреннийBuffer<T> класс для увеличения массива) илиToList() (который вызываетList<T>(IEnumerable<T>) constructor) в конечном итоге будет заключаться в том, чтобы поместить их в массив и увеличивать массив до тех пор, пока он не подойдет вс

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

 Scott Rippey18 дек. 2013 г., 18:45
@ EldritchConundrum 25% исходит из этой логики: если количество элементов неизвестно, то вызовToList илиToArray начнёт с создания небольшого буфера. Когда этот буфер заполнен, он удваивает емкость буфера и продолжает работу. Поскольку емкость всегда удваивается, неиспользуемый буфер всегда будет между 0% и 50%.
 jpierson28 июн. 2010 г., 22:04
Интересный факт, с которым я столкнулся, заключается в том, что для коррелированных запросов, вызванных использованием группы, определенной посредством объединения групп, в вашей проекции, Linq to SQL добавляет еще один подзапрос для получения счетчика для этой группы. Я предполагаю, что это означает, что в этих случаях размер коллекции будет известен до того, как элементы будут извлечены, и, таким образом, может быть создан массив точного размера, который сэкономит ресурсы обработки и памяти при материализации результатов.
 Scott Rippey06 окт. 2014 г., 23:31
@ AndyClaw ОбаList а такжеBuffer проверит наICollection, в этом случае производительность будет одинаковой.
 Scott Rippey26 окт. 2010 г., 23:35
Если граф известен заранее, производительность идентична. Однако, если Count не известен заранее, единственная разница междуToArray() а такжеToList() заключается в том, что первый должен обрезать избыток, который включает в себя копирование всего массива, тогда как последний не обрезает избыток, а использует в среднем на 25% больше памяти. Это будет иметь последствия, только если тип данных большstruct. Просто пища для размышлений.
 AndyClaw06 окт. 2014 г., 16:51
@ ScottRippey Я только что искал источник нового List из источника IEnumerable, и он проверяет, является ли IEnumerable ICollection, и если это так, то он начинает с выделения одного массива с точным размером, необходимым из свойства Count, поэтому в этом случае ToList () определенно будет быстрее. Полный ответ может включать этот факт, хотя я не думаю, что это самый распространенный случай.

кто заинтересован в использовании этого результата в другом Linq-to-sql, например,

from q in context.MyTable
where myListOrArray.Contains(q.someID)
select q;

затем сгенерированный SQL одинаков независимо от того, использовали ли вы List или Array для myListOrArray. Теперь я знаю, что некоторые могут спросить, почему даже перечислять перед этим оператором, но есть разница между SQL, сгенерированным из IQueryable vs (List или Array).

что других эталонных тестов здесь не хватает, так что вот мой недостаток. Дайте мне знать, если вы нашли что-то не так с моей методологией.

/* This is a benchmarking template I use in LINQPad when I want to do a
 * quick performance test. Just give it a couple of actions to test and
 * it will give you a pretty good idea of how long they take compared
 * to one another. It's not perfect: You can expect a 3% error margin
 * under ideal circumstances. But if you're not going to improve
 * performance by more than 3%, you probably don't care anyway.*/
void Main()
{
    // Enter setup code here
    var values = Enumerable.Range(1, 100000)
        .Select(i => i.ToString())
        .ToArray()
        .Select(i => i);
    values.GetType().Dump();
    var actions = new[]
    {
        new TimedAction("ToList", () =>
        {
            values.ToList();
        }),
        new TimedAction("ToArray", () =>
        {
            values.ToArray();
        }),
        new TimedAction("Control", () =>
        {
            foreach (var element in values)
            {
                // do nothing
            }
        }),
        // Add tests as desired
    };
    const int TimesToRun = 1000; // Tweak this as necessary
    TimeActions(TimesToRun, actions);
}


#region timer helper methods
// Define other methods and classes here
public void TimeActions(int iterations, params TimedAction[] actions)
{
    Stopwatch s = new Stopwatch();
    int length = actions.Length;
    var results = new ActionResult[actions.Length];
    // Perform the actions in their initial order.
    for (int i = 0; i < length; i++)
    {
        var action = actions[i];
        var result = results[i] = new ActionResult { Message = action.Message };
        // Do a dry run to get things ramped up/cached
        result.DryRun1 = s.Time(action.Action, 10);
        result.FullRun1 = s.Time(action.Action, iterations);
    }
    // Perform the actions in reverse order.
    for (int i = length - 1; i >= 0; i--)
    {
        var action = actions[i];
        var result = results[i];
        // Do a dry run to get things ramped up/cached
        result.DryRun2 = s.Time(action.Action, 10);
        result.FullRun2 = s.Time(action.Action, iterations);
    }
    results.Dump();
}

public class ActionResult
{
    public string Message { get; set; }
    public double DryRun1 { get; set; }
    public double DryRun2 { get; set; }
    public double FullRun1 { get; set; }
    public double FullRun2 { get; set; }
}

public class TimedAction
{
    public TimedAction(string message, Action action)
    {
        Message = message;
        Action = action;
    }
    public string Message { get; private set; }
    public Action Action { get; private set; }
}

public static class StopwatchExtensions
{
    public static double Time(this Stopwatch sw, Action action, int iterations)
    {
        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();

        return sw.Elapsed.TotalMilliseconds;
    }
}
#endregion

Вы можете Загрузите скрипт LINQPad здесь.

Полученные результаты:

Изменив приведенный выше код, вы обнаружите, что:

Разница менее значима, когда работа с меньшими массивами. Разница менее значительна при работе сints, а неstring S. Используя большойstruct вместоstrings обычно занимает намного больше времени, но не сильно меняет соотношение.

Это согласуется с выводами, получившими наибольшее количество ответов:

Вы вряд ли заметите разницу в производительности, если ваш код часто создает много больших списков данных. (При создании 1000 списков по 100 тыс. Строк в каждой разнице было только 200 мс.)ToList() постоянно работает быстрее и будет лучшим выбором, если вы не планируете долго держаться за результаты.Обновит

@ JonHanna указал, что в зависимости от реализацииSelect это возможно дляToList() илиToArray() реализация, чтобы заранее предсказать размер результирующей коллекции. Замена.Select(i => i) в приведенном выше коде сWhere(i => true) дает очень похожие результаты в настоящее время, и, скорее всего, это будет сделано независимо от реализации .NET.

 StriplingWarrior08 сент. 2017 г., 21:46
@ JonHanna: Спасибо за быстрый отзыв. Я не знал, что .NET Core делает эту оптимизацию. Это круто. В моем коде.Select(i => i) можно заменить на.Where(i => true) чтобы исправить это.
 Jon Hanna08 сент. 2017 г., 21:27
В .NET Core оба случая должны быть лучше, чем в netfx, потому что он поймет, что размер будет100000 и используйте это для оптимизации обоихToList() а такжеToArray(), сToA,rray() он немного легче, потому что ему не нужна операция сжатия, в которой он нуждался бы, в противном случаеToList() имеет преимущество. Пример в вопросе все равно проиграет, потому чтоWhere означает, что такое предсказание размера не может быть сделано.
 Jon Hanna08 сент. 2017 г., 22:08
Да, это остановило бы оптимизацию, влияющую на это на corefx. Может быть интересно иметь как размер, так и степень двойки (которая должна даватьToArray() преимущество), а не то, что выше, и сравните результаты.
 StriplingWarrior08 сент. 2017 г., 23:39
@ JonHanna: интересно,ToArray() все еще проигрывает в лучшем случае. СMath.Pow(2, 15) elements, это (ToList: 700мс, ToArray: 900мс). Добавление еще одного элемента поднимает его до (ToList: 925, ToArray: 1350). Интересно, еслиToArray все еще копирует массив, даже когда он уже имеет идеальный размер? Они, вероятно, полагали, что это было достаточно редкое явление, что оно не стоило дополнительных условий.
 Jon Hanna09 сент. 2017 г., 00:02
Он не копировал при точном соответствии размера, даже до того, как мы начали оптимизировать его в corefx, так что это тот случай, когда он получает наибольшее количество разрывов.
Решение Вопроса

вы должны использоватьToList. В большинстве сценариевToArray выделит больше памяти, чемToList.

Оба используют массивы для хранения, ноToList имеет более гибкое ограничение. Массив должен быть не меньше, чем количество элементов в коллекции. Если массив больше, это не проблема. Тем не мениеToArray нужен размер массива, точно соответствующий количеству элементов.

Чтобы встретить это ограничениеToArray часто делает еще одно распределение, чемToList. Как только у него есть достаточно большой массив, он выделяет массив, который имеет точный размер, и копирует элементы обратно в этот массив. Единственный раз, когда этого можно избежать, это когда алгоритм увеличения массива просто совпадает с количеством элементов, которые необходимо сохранить (определенно в меньшинстве).

РЕДАКТИРОВАТ

Несколько человек спросили меня о последствиях наличия дополнительной неиспользуемой памяти вList<T> ценность

Это действительная проблема. Если созданная коллекция является долгоживущей, никогда не изменяется после создания и имеет высокую вероятность попадания в кучу Gen2, тогда вам лучше взять дополнительное выделениеToArray впереди.

В общем, я нахожу это более редким случаем. Гораздо чаще можно увидеть многоToArray вызовы, которые немедленно передаются другим недолгим использованиям памяти, в этом случаеToList явно лучше.

Ключевым моментом здесь является профиль, профиль, а затем еще немного профиль.

 Timbo06 сент. 2014 г., 00:38
@ RoyiNamir, поскольку ToArray сначала выполняет распределение в стиле ToList с накладными расходами, а затем выполняет дополнительное распределение точного размера.
 Royi Namir25 мая 2014 г., 13:56
@ JaredPar Я не понимаю, какToArray Может выделять больше памяти, если ему нужен точный размер местоположения, гдеToList<> @ очевидно есть автоматические запасные локации. (Autoincrease)
 Anthony Pegram01 мая 2013 г., 20:00
С другой стороны, разве лишняя память, выделенная на работу по созданию массива, не будет пригодна для сборки мусора, тогда как дополнительные издержки для Списка останутся? Я говорю, держи это проще. Если вам нужно добавить или удалить элементы, для этого есть инструмент. Если вы этого не сделаете, есть другой инструмент для этого. Используйте тот, который имеет смысл. Если позже вы обнаружите проблему с памятью и производительностью, и это все, Измени это
 JaredPar01 мая 2013 г., 20:22
@ AnthonyPegram да, это правильное соображение. Если значение используется в долговременном хранилище, не будет изменено и потенциально превратится в Gen 2, тогда вам лучше заплатить дополнительное выделение, чем загрязнять кучу Gen 2. IME, хотя я редко вижу это. Гораздо чаще можно увидеть, как ToArray сразу передается на другой недолгий запрос LINQ.
 JaredPar01 мая 2013 г., 20:27
@ AnthonyPegram я обновил свой ответ, чтобы включить эту сторону обсуждения

что разница в производительности должна быть незначительной. Тем не менее, я хотел проверить это, чтобы быть уверенным, поэтому я сделал - и это незначительно.

Testing with List<T> source:
ToArray time: 1934 ms (0.01934 ms/call), memory used: 4021 bytes/array
ToList  time: 1902 ms (0.01902 ms/call), memory used: 4045 bytes/List

Testing with array source:
ToArray time: 1957 ms (0.01957 ms/call), memory used: 4021 bytes/array
ToList  time: 2022 ms (0.02022 ms/call), memory used: 4045 bytes/List

Каждый исходный массив / список имел 1000 элементов. Таким образом, вы можете видеть, что разница во времени и в памяти незначительна.

Мой вывод: вы могли бы также использоватьСоставлять список(, так какList<T> обеспечивает больше функциональности, чем массив, если только для вас не имеет значения несколько байт памяти.

 Scott Rippey20 мая 2011 г., 18:20
Интересно, будет ли этот результат другим, если вы используете большойstruct вместо примитивного типа или класса.
 Mohammad Dehghan25 февр. 2014 г., 08:32
-1 ПоведениеToArray() а такжеToList() отличаются слишком сильно, когда они снабженыICollection<T> параметр - они просто выполняют одно выделение и одну операцию копирования. И то и другоеList<T> а такжеArray воплощать в жизньICollection<T>, значит, ваши тесты вообще не действительны.
 Grigory30 сент. 2011 г., 14:31
List <T> .ToList ???? Какой смысл? Лучше попытаться добавить в него IEnumerable, который не реализует интерфейс ICollection.
 EMP30 сент. 2011 г., 16:06
Я хотел убедиться, что я измеряю только времяToList илиToArray вызов, а не перечисление любогоIEnumerable. List <T> .ToList () по-прежнему создает новый List <T> - он не просто «возвращает это».
 StriplingWarrior08 сент. 2017 г., 21:20
Для всех интересующихся, я отправил мой собственный тест в качестве отдельного ответа. Оно использует.Select(i => i) чтобы избежатьICollection<T> проблема реализации, и включает контрольную группу, чтобы увидеть, сколько времени уходит на итерацию по источникуIEnumerable<> в первую очередь

Редактироват: Последняя часть этого ответа недействительна. Тем не менее, остальное все еще полезная информация, поэтому я оставлю ее.

Я знаю, что это старый пост, но после того же вопроса и исследования я нашел кое-что интересное, чем стоит поделиться.

Во-первых, я согласен с @mquander и его ответом. Он прав, говоря, что по производительности они идентичны.

Однако я использовал Reflector, чтобы взглянуть на методы вSystem.Linq.Enumerable пространство имен расширений, и я заметил очень распространенную оптимизацию.
По возможности,IEnumerable<T> источник приведен кIList<T> илиICollection<T> чтобы оптимизировать метод. Например, посмотрите наElementAt(int).

Интересно, что Microsoft решила оптимизировать только дляIList<T>, но нетIList. Похоже, Microsoft предпочитает использоватьIList<T> интерфейс.

System.Array только реализуетIList, поэтому он не выиграет ни от одной из этих оптимизаций расширения.
Поэтому я утверждаю, что лучше всего использовать.ToList() метод.
Если вы используете какой-либо из методов расширения или передаете список другому методу, есть вероятность, что он может быть оптимизирован дляIList<T>.

 Scott Rippey12 июл. 2010 г., 22:41
Я сделал тест и обнаружил что-то удивительное. Массив реализует IList <T>! Использование Reflector для анализа System.Array показывает только цепочку наследования IList, ICollection, IEnumerable, но используя отражение во время выполнения, я обнаружил, что string [] имеет цепочку наследования IList, ICollection, IEnumerable, IList <string>, ICollection <string >, IEnumerable <string>. Поэтому у меня нет лучшего ответа, чем @mquander!
 AnorZaken07 окт. 2015 г., 20:48
@ ScottRippey Да. Странное наблюдение, которое вы заметили, на самом деле является частью «хака» - и оно также имеет некоторые довольно странные последствия в отношении «фиксированного размера» и аналогичных свойств (с некоторыми несоответствиями в зависимости от того, как вы их разыгрываете). Есть несколько довольно больших комментариев, касающихся этой темы, внутри исходного кода .net. Извините за отсутствие ссылок, но если я правильно помню, это довольно легко найти (внутри класса-массива). (И еще есть большой вопрос, обсуждающий несоответствия .... где-то ...> __>)

которые сталкиваются с ним, существует также и альтернатива 'Memoizing' для Enumerable - который имеет эффект кэширования и остановки многократного перечисления оператора Linq, что и является ToArray ( ) и ToList () используются много, хотя атрибуты коллекции списка или массива никогда не используются.

Memoize доступен в библиотеке RX / System.Interactive и объясняется здесь: Больше LINQ с System.Interactive

(От Блог Барта Де Смета который являетсяочен рекомендуется читать, если вы много работаете с Linq to Objects)

который возвращает @ Только для чтен ICollection<T>. Это может быть лучше, чем при использованииToList илиToArray когда вы не хотите использовать ни свойства индексации массива / списка, ни добавлять / удалять из списка.

public static class EnumerableExtension
{
    /// <summary>
    /// Causes immediate evaluation of the linq but only if required.
    /// As it returns a readonly ICollection, is better than using ToList or ToArray
    /// when you do not want to use the indexing properties of an IList, or add to the collection.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumerable"></param>
    /// <returns>Readonly collection</returns>
    public static ICollection<T> Evaluate<T>(this IEnumerable<T> enumerable)
    {
        //if it's already a readonly collection, use it
        var collection = enumerable as ICollection<T>;
        if ((collection != null) && collection.IsReadOnly)
        {
            return collection;
        }
        //or make a new collection
        return enumerable.ToList().AsReadOnly();
    }
}

Единичные тесты:

[TestClass]
public sealed class EvaluateLinqTests
{
    [TestMethod]
    public void EvalTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResult = list.Select(i => i);
        var linqResultEvaluated = list.Select(i => i).Evaluate();
        list.Clear();
        Assert.AreEqual(0, linqResult.Count());
        //even though we have cleared the underlying list, the evaluated list does not change
        Assert.AreEqual(3, linqResultEvaluated.Count());
    }

    [TestMethod]
    public void DoesNotSaveCreatingListWhenHasListTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        //list is not readonly, so we expect a new list
        Assert.AreNotSame(list, linqResultEvaluated);
    }

    [TestMethod]
    public void SavesCreatingListWhenHasReadonlyListTest()
    {
        var list = new List<int> {1, 2, 3}.AsReadOnly();
        var linqResultEvaluated = list.Evaluate();
        //list is readonly, so we don't expect a new list
        Assert.AreSame(list, linqResultEvaluated);
    }

    [TestMethod]
    public void SavesCreatingListWhenHasArrayTest()
    {
        var list = new[] {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        //arrays are readonly (wrt ICollection<T> interface), so we don't expect a new object
        Assert.AreSame(list, linqResultEvaluated);
    }

    [TestMethod]
    [ExpectedException(typeof (NotSupportedException))]
    public void CantAddToResultTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        Assert.AreNotSame(list, linqResultEvaluated);
        linqResultEvaluated.Add(4);
    }

    [TestMethod]
    [ExpectedException(typeof (NotSupportedException))]
    public void CantRemoveFromResultTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        Assert.AreNotSame(list, linqResultEvaluated);
        linqResultEvaluated.Remove(1);
    }
}
 weston07 нояб. 2014 г., 11:21
@ tne Заметьте, что я делаю Tolist до AsReadOnly, так что нет никаких ссылок на основной изменяемый файл.
 tne07 нояб. 2014 г., 11:14
Стоит отметить, что договор о коллекции только для чтения предусматривает, что пользователь объекта не может изменять его, но владелец может сделать это, если он сохраняет ссылку на него, который предлагает изменяемый интерфейс. Для интерфейсов, которые гарантируют, что базовая структура никогда не изменится, посмотрите на неизменные коллекции. Что касается того, почему неизменяемые или доступные только для чтения или простые коллекции для чтения и записи лучше или хуже, для сравнения нужен эталон; окончательного ответа нет (иначе нам не пришлось бы выбирать).
 tne07 нояб. 2014 г., 11:46
Вы совершенно правы, и это был, вероятно, лучший способ сделать что-то, прежде чем неизменные коллекции поступили в BCL (первая бета вышла через месяц после вашего ответа).
 doug6553620 окт. 2016 г., 01:21
оллекции @Imutable существуют для безопасности потоков, где потоки могут предполагать, что она не изменится, и если это произойдет, будет создана новая версия, вместо того, чтобы конкурировать с читателями и менять ее, пока они ее используют. Таким образом, никому не нужно приобретать замок.

ToListAsync<T>() является предпочтительным.

В Entity Framework 6 оба метода в конечном итоге вызывают один и тот же внутренний метод, ноToArrayAsync<T>() звонкиlist.ToArray() в конце, который реализован как

T[] array = new T[_size];
Array.Copy(_items, 0, array, 0, _size);
return array;

ТакToArrayAsync<T>() имеет некоторые накладные расходы, поэтоToListAsync<T>() является предпочтительным.

 Shimmy24 дек. 2018 г., 07:17
Это и есть тот ответ, который я искал, как EF это делает. Мне было бы любопытно, как это в EF Core.

Согласно источнику System.Linq.Enumerable, ToList просто верниnew List(source), в то время какToArray использоватьnew Buffer<T>(source).ToArray() чтобы вернутьT[].

О распределении памяти:

Пока работает наIEnumerable<T> тольк объект,ToArray выделять память больше, чемToList. Но вам не нужно заботиться об этом в большинстве случаев, потому что GC будет выполнять сборку мусора при необходимости.

Об эффективном времени выполнения:

Те, кто задают этот вопрос, могут запустить следующий код на своей машине, и вы получите ответ.

class PersonC
{
    public Guid uuid;
    public string name;
    public int age;
    public bool sex;
    public DateTime BirthDay;
    public double weight;
}

struct PersonS
{
    public Guid uuid;
    public string name;
    public int age;
    public bool sex;
    public DateTime BirthDay;
    public double weight;
}

class PersonT<T> : IEnumerable<T>
{
    private List<T> items;
    public PersonT(IEnumerable<T> init)
    {
        items = new List<T>(init);
    }

    public IEnumerator<T> GetEnumerator() => items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator();
}

private IEnumerable<PersonC> C(int count)
{
    for (var i = 0; i < count; ++i)
    {
        var guid = Guid.NewGuid();
        var guidBytes = guid.ToByteArray(); //16 bytes
        yield return new PersonC
        {
            uuid = guid,
            name = guid.ToString(),
            age = guidBytes[0] ^ guidBytes[7],
            sex = guidBytes[14] % 2 == 0,
            BirthDay = DateTime.Now.AddDays(-guidBytes[11] * 18),
            weight = guidBytes[12] * 100
        };
    }
}

private IEnumerable<PersonS> S(int count)
{
    for (var i = 0; i < count; ++i)
    {
        var guid = Guid.NewGuid();
        var guidBytes = guid.ToByteArray(); //16 bytes
        yield return new PersonS
        {
            uuid = guid,
            name = guid.ToString(),
            age = guidBytes[0] ^ guidBytes[7],
            sex = guidBytes[14] % 2 == 0,
            BirthDay = DateTime.Now.AddDays(-guidBytes[11] * 18),
            weight = guidBytes[12] * 100
        };
    }
}

private void MakeLog(string test, List<long> log) =>
    Console.WriteLine("{0} {1} ms -> [{2}]",
        test,
        log.Average(),
        string.Join(", ", log)
    );

private void Test1(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    MakeLog("C.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = C(count).ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = C(count).ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = S(count).ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = S(count).ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void Test2(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    var dataC1 = new PersonT<PersonC>(C(count));
    var dataS1 = new PersonT<PersonS>(S(count));

    MakeLog("C1.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC1.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C1.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC1.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S1.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS1.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S1.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS1.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void Test3(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    var dataC2 = (ICollection<PersonC>) new List<PersonC>(C(count));
    var dataS2 = (ICollection<PersonS>) new List<PersonS>(S(count));

    MakeLog("C2.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC2.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C2.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC2.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S2.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS2.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S2.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS2.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void TestMain()
{
    const int times = 100;
    const int count = 1_000_000 + 1;
    Test1(times, count);
    Test2(times, count);
    Test3(times, count);
}

Я получил эти результаты на своей машине:

Группа 1

C.ToList 761.79 ms -> [775, 755, 759, 759, 756, 759, 765, 750, 757, 762, 759, 754, 757, 753, 763, 753, 759, 756, 768, 754, 763, 757, 757, 777, 780, 758, 754, 758, 762, 754, 758, 757, 763, 758, 760, 754, 761, 755, 764, 847, 952, 755, 747, 763, 760, 758, 754, 763, 761, 758, 750, 764, 757, 763, 762, 756, 753, 759, 759, 757, 758, 779, 765, 760, 760, 756, 760, 756, 755, 764, 759, 753, 757, 760, 752, 764, 758, 760, 758, 760, 755, 761, 751, 753, 761, 762, 761, 758, 759, 752, 765, 756, 760, 755, 757, 753, 760, 751, 755, 779]
C.ToArray 782.56 ms -> [783, 774, 771, 771, 773, 774, 775, 775, 772, 770, 771, 774, 771, 1023, 975, 772, 767, 776, 771, 779, 772, 779, 775, 771, 775, 773, 775, 771, 765, 774, 770, 781, 772, 771, 781, 762, 817, 770, 775, 779, 769, 774, 763, 775, 777, 769, 777, 772, 775, 778, 775, 771, 770, 774, 772, 769, 772, 769, 774, 775, 768, 775, 769, 774, 771, 776, 774, 773, 778, 769, 778, 767, 770, 787, 783, 779, 771, 768, 805, 780, 779, 767, 773, 771, 773, 785, 1044, 853, 775, 774, 775, 771, 770, 769, 770, 776, 770, 780, 821, 770]
S.ToList 704.2 ms -> [687, 702, 709, 691, 694, 710, 696, 698, 700, 694, 701, 719, 706, 694, 702, 699, 699, 703, 704, 701, 703, 705, 697, 707, 691, 697, 707, 692, 721, 698, 695, 700, 704, 700, 701, 710, 700, 705, 697, 711, 694, 700, 695, 698, 701, 692, 696, 702, 690, 699, 708, 700, 703, 714, 701, 697, 700, 699, 694, 701, 697, 696, 699, 694, 709, 1068, 690, 706, 699, 699, 695, 708, 695, 704, 704, 700, 695, 704, 695, 696, 702, 700, 710, 708, 693, 697, 702, 694, 700, 706, 699, 695, 706, 714, 704, 700, 695, 697, 707, 704]
S.ToArray 742.5 ms -> [742, 743, 733, 745, 741, 724, 738, 745, 728, 732, 740, 727, 739, 740, 726, 744, 758, 732, 744, 745, 730, 739, 738, 723, 745, 757, 729, 741, 736, 724, 744, 756, 739, 766, 737, 725, 741, 742, 736, 748, 742, 721, 746, 1043, 806, 747, 731, 727, 742, 742, 726, 738, 746, 727, 739, 743, 730, 744, 753, 741, 739, 746, 728, 740, 744, 734, 734, 738, 731, 747, 736, 731, 765, 735, 726, 740, 743, 730, 746, 742, 725, 731, 757, 734, 738, 741, 732, 747, 744, 721, 742, 741, 727, 745, 740, 730, 747, 760, 737, 740]

C1.ToList 32.34 ms -> [35, 31, 31, 31, 32, 31, 31, 31, 31, 31, 31, 31, 31, 31, 33, 32, 31, 31, 31, 31, 30, 32, 31, 31, 31, 31, 32, 30, 31, 31, 31, 30, 32, 31, 31, 31, 36, 31, 31, 31, 32, 30, 31, 32, 31, 31, 31, 31, 31, 32, 31, 31, 31, 31, 33, 32, 31, 32, 31, 31, 33, 31, 31, 31, 31, 31, 32, 31, 32, 31, 34, 38, 68, 42, 79, 33, 31, 31, 31, 31, 31, 30, 30, 30, 30, 31, 31, 31, 31, 32, 31, 32, 31, 31, 31, 32, 33, 33, 31, 31]
C1.ToArray 56.32 ms -> [57, 56, 59, 54, 54, 55, 56, 57, 54, 54, 55, 55, 57, 56, 59, 57, 56, 58, 56, 56, 54, 56, 57, 55, 55, 55, 57, 58, 57, 58, 55, 55, 56, 55, 57, 56, 56, 59, 56, 56, 56, 56, 58, 56, 57, 56, 56, 57, 56, 55, 56, 56, 56, 59, 56, 56, 56, 55, 55, 54, 55, 54, 57, 56, 56, 56, 55, 55, 56, 56, 56, 59, 56, 56, 57, 56, 57, 56, 56, 56, 56, 62, 55, 56, 56, 56, 69, 57, 58, 56, 57, 58, 56, 57, 56, 56, 56, 56, 56, 56]
S1.ToList 88.69 ms -> [96, 90, 90, 89, 91, 88, 89, 90, 96, 89, 89, 89, 90, 90, 90, 89, 90, 90, 89, 90, 89, 91, 89, 91, 89, 91, 89, 90, 90, 89, 87, 88, 87, 88, 87, 87, 87, 87, 88, 88, 87, 87, 89, 87, 87, 87, 91, 88, 87, 86, 89, 87, 90, 89, 89, 90, 89, 87, 87, 87, 86, 87, 88, 90, 88, 87, 87, 92, 87, 87, 88, 88, 88, 86, 86, 87, 88, 87, 87, 87, 89, 87, 89, 87, 90, 89, 89, 89, 91, 89, 90, 89, 90, 88, 90, 90, 90, 88, 89, 89]
S1.ToArray 143.26 ms -> [130, 129, 130, 131, 133, 130, 131, 130, 135, 137, 130, 136, 132, 131, 130, 131, 132, 130, 132, 136, 130, 131, 157, 153, 194, 364, 176, 189, 203, 194, 189, 192, 183, 140, 142, 147, 145, 134, 159, 158, 142, 167, 130, 143, 145, 144, 160, 154, 156, 153, 153, 164, 142, 145, 137, 134, 145, 143, 142, 135, 133, 133, 135, 134, 134, 139, 139, 133, 134, 141, 133, 132, 133, 132, 133, 131, 135, 132, 133, 132, 128, 128, 130, 132, 129, 129, 129, 129, 129, 128, 134, 129, 129, 129, 129, 128, 128, 137, 130, 131]

C2.ToList 3.25 ms -> [5, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3]
C2.ToArray 3.37 ms -> [4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 4, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 4, 3, 3, 4, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 4, 4, 3, 4, 4, 3, 3, 4, 3, 3, 4, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 4, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3]
S2.ToList 37.72 ms -> [38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 40, 38, 38, 39, 39, 38, 38, 38, 38, 37, 37, 37, 37, 39, 37, 37, 39, 38, 37, 37, 37, 37, 39, 38, 37, 37, 38, 37, 38, 37, 37, 38, 37, 37, 37, 38, 37, 37, 36, 37, 38, 37, 39, 37, 39, 38, 37, 38, 38, 38, 38, 38, 38, 37, 38, 38, 38, 38, 38, 37, 38, 37, 37, 38, 37, 37, 39, 41, 37, 38, 38, 37, 37, 37, 37, 38, 37, 37, 37, 40, 37, 37, 37, 37, 39, 38]
S2.ToArray 38.86 ms -> [39, 37, 39, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 39, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 38, 38, 38, 39, 37, 38, 38, 38, 38, 38, 37, 37, 38, 37, 37, 38, 38, 40, 38, 38, 38, 38, 38, 39, 38, 38, 39, 38, 38, 39, 38, 38, 40, 38, 39, 38, 38, 39, 38, 38, 38, 38, 38, 39, 38, 38, 38, 39, 39, 37, 38, 38, 39, 71, 78, 37, 37, 37, 39, 38, 38, 39, 38, 38, 38, 38, 38, 39, 38, 38, 38, 39, 38, 38, 38]

Group2:

C.ToList 756.81 ms
C.ToArray 774.21 ms
S.ToList 709.7 ms
S.ToArray 753.51 ms

C1.ToList 32.06 ms
C1.ToArray 56.58 ms
S1.ToList 89.43 ms
S1.ToArray 132.85 ms

C2.ToList 3.45 ms
C2.ToArray 3.36 ms
S2.ToList 41.43 ms
S2.ToArray 40.84 ms

Group3:

C.ToList 756.64 ms
C.ToArray 771.56 ms
S.ToList 705.42 ms
S.ToArray 749.59 ms

C1.ToList 31.45 ms
C1.ToArray 57.03 ms
S1.ToList 91.26 ms
S1.ToArray 129.77 ms

C2.ToList 3.26 ms
C2.ToArray 3.29 ms
S2.ToList 41.57 ms
S2.ToArray 40.69 ms

Group4:

C.ToList 729.65 ms -> [749, 730, 721, 719, 723, 743, 721, 724, 727, 722, 716, 725, 723, 726, 718, 722, 731, 722, 723, 725, 723, 722, 728, 726, 728, 718, 726, 1088, 788, 737, 729, 710, 730, 728, 717, 723, 728, 721, 722, 728, 722, 736, 723, 729, 732, 724, 726, 727, 728, 728, 726, 726, 725, 727, 725, 728, 728, 718, 724, 725, 726, 724, 726, 729, 727, 722, 722, 725, 725, 728, 724, 727, 738, 717, 726, 723, 725, 725, 727, 724, 720, 726, 726, 723, 727, 730, 723, 721, 725, 727, 727, 733, 720, 722, 722, 725, 722, 725, 728, 726]
C.ToArray 788.36 ms -> [748, 740, 742, 797, 1090, 774, 781, 787, 784, 786, 786, 782, 781, 781, 784, 783, 783, 781, 783, 787, 783, 784, 775, 789, 784, 785, 778, 774, 781, 783, 786, 781, 780, 788, 778, 785, 777, 781, 786, 782, 781, 787, 782, 787, 784, 773, 783, 782, 781, 777, 783, 781, 785, 788, 777, 776, 784, 784, 783, 789, 778, 781, 791, 768, 779, 783, 781, 787, 786, 781, 784, 781, 785, 781, 780, 809, 1155, 780, 790, 789, 783, 776, 785, 783, 786, 787, 782, 782, 787, 777, 779, 784, 783, 776, 786, 775, 782, 779, 784, 784]
S.ToList 705.54 ms -> [690, 705, 709, 708, 702, 707, 703, 696, 703, 702, 700, 703, 700, 707, 705, 699, 697, 703, 695, 698, 707, 697, 711, 710, 699, 700, 708, 707, 693, 710, 704, 691, 702, 700, 703, 700, 705, 700, 703, 695, 709, 705, 698, 699, 709, 700, 699, 704, 691, 705, 703, 700, 708, 1048, 710, 706, 706, 692, 702, 705, 695, 701, 710, 697, 698, 706, 705, 707, 707, 695, 698, 704, 698, 699, 705, 698, 703, 702, 701, 697, 702, 702, 704, 703, 699, 707, 703, 705, 701, 717, 698, 695, 713, 696, 708, 705, 697, 699, 700, 698]
S.ToArray 745.01 ms -> [751, 743, 727, 734, 736, 745, 739, 750, 739, 750, 758, 739, 744, 738, 730, 744, 745, 739, 744, 750, 733, 735, 743, 731, 749, 748, 727, 746, 749, 731, 737, 803, 1059, 756, 769, 748, 740, 745, 741, 746, 749, 732, 741, 742, 732, 744, 746, 737, 742, 739, 733, 744, 741, 729, 746, 760, 725, 741, 764, 739, 750, 751, 727, 745, 738, 727, 735, 741, 720, 736, 740, 733, 741, 746, 731, 749, 756, 740, 738, 736, 732, 741, 741, 733, 741, 744, 736, 742, 742, 735, 743, 746, 729, 748, 765, 743, 734, 742, 728, 749]

C1.ToList 32.27 ms -> [36, 31, 31, 32, 31, 32, 31, 30, 32, 30, 30, 30, 34, 32, 31, 31, 31, 31, 31, 31, 31, 32, 38, 51, 68, 57, 35, 30, 31, 31, 30, 30, 33, 30, 31, 34, 31, 34, 32, 31, 31, 31, 31, 32, 30, 30, 31, 30, 31, 31, 32, 31, 31, 31, 32, 31, 31, 31, 32, 31, 33, 31, 31, 32, 30, 30, 30, 30, 30, 33, 30, 33, 32, 31, 30, 31, 31, 32, 32, 31, 35, 31, 34, 31, 31, 32, 31, 31, 32, 31, 32, 31, 31, 35, 31, 31, 31, 31, 31, 32]
C1.ToArray 56.72 ms -> [58, 56, 57, 57, 59, 58, 58, 57, 56, 59, 57, 55, 55, 54, 56, 55, 56, 56, 57, 59, 56, 55, 58, 56, 55, 55, 55, 55, 58, 58, 55, 57, 57, 56, 57, 57, 57, 57, 59, 59, 56, 57, 56, 57, 57, 56, 57, 59, 58, 56, 57, 57, 57, 58, 56, 56, 59, 56, 59, 57, 57, 57, 57, 59, 57, 56, 57, 56, 58, 56, 57, 56, 57, 59, 55, 58, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 56, 56, 57, 56, 56, 57, 58, 57, 57, 57, 57, 57]
S1.ToList 90.72 ms -> [95, 90, 90, 89, 89, 89, 91, 89, 89, 87, 91, 89, 89, 89, 91, 89, 89, 89, 90, 89, 89, 90, 88, 89, 88, 90, 89, 90, 89, 89, 90, 90, 89, 89, 90, 91, 89, 91, 89, 90, 89, 89, 90, 91, 89, 89, 89, 89, 89, 89, 90, 89, 89, 89, 90, 89, 90, 89, 91, 89, 90, 89, 90, 89, 90, 89, 96, 89, 90, 89, 89, 89, 89, 89, 90, 89, 89, 89, 90, 87, 89, 90, 90, 91, 89, 91, 89, 89, 90, 91, 90, 89, 93, 144, 149, 90, 90, 89, 89, 89]
S1.ToArray 131.4 ms -> [130, 128, 127, 134, 129, 129, 130, 136, 131, 130, 132, 132, 133, 131, 132, 131, 133, 132, 130, 131, 132, 131, 130, 133, 133, 130, 130, 131, 131, 131, 132, 134, 131, 131, 132, 131, 132, 131, 134, 131, 131, 130, 131, 131, 130, 132, 129, 131, 131, 131, 132, 131, 133, 134, 131, 131, 132, 132, 131, 133, 131, 131, 130, 133, 131, 130, 134, 132, 131, 132, 132, 131, 131, 134, 131, 131, 132, 132, 131, 130, 138, 130, 130, 131, 132, 132, 130, 134, 131, 131, 132, 131, 130, 132, 133, 131, 131, 131, 130, 131]

C2.ToList 3.21 ms -> [4, 3, 3, 3, 4, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 3, 3, 3]
C2.ToArray 3.22 ms -> [4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 4]
S2.ToList 41.46 ms -> [42, 40, 41, 40, 42, 40, 40, 40, 40, 40, 40, 40, 40, 41, 40, 40, 41, 40, 40, 40, 39, 41, 41, 39, 40, 40, 43, 40, 39, 40, 40, 40, 40, 40, 40, 41, 40, 40, 40, 43, 40, 43, 75, 76, 47, 39, 40, 40, 40, 40, 42, 40, 41, 40, 40, 40, 44, 41, 40, 42, 42, 40, 41, 41, 41, 41, 41, 40, 41, 41, 41, 41, 42, 41, 40, 41, 41, 42, 42, 41, 40, 41, 41, 41, 41, 41, 40, 42, 40, 42, 41, 41, 41, 43, 41, 41, 41, 41, 42, 41]
S2.ToArray 41.14 ms -> [42, 41, 41, 40, 40, 40, 40, 41, 41, 42, 41, 42, 41, 41, 41, 42, 41, 41, 42, 41, 41, 41, 41, 41, 42, 40, 41, 40, 42, 40, 42, 41, 40, 42, 41, 41, 43, 42, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 40, 40, 41, 41, 41, 40, 42, 41, 41, 41, 41, 41, 40, 41, 41, 42, 41, 41, 41, 42, 41, 41, 41, 41, 41, 41, 42, 42, 42, 41, 45, 46, 41, 40, 41, 41, 42, 41, 41, 41, 41, 41, 41, 40, 41, 43, 40, 40, 40, 40, 43, 41]

Group5:

C.ToList 757.06 ms -> [770, 752, 752, 751, 778, 763, 761, 763, 747, 758, 748, 747, 754, 749, 752, 753, 756, 762, 750, 753, 756, 749, 755, 757, 755, 756, 755, 744, 753, 758, 747, 751, 759, 751, 761, 755, 746, 752, 752, 749, 746, 752, 753, 755, 752, 755, 754, 754, 966, 937, 749, 759, 748, 747, 754, 749, 755, 750, 746, 754, 757, 752, 753, 745, 758, 755, 761, 753, 751, 755, 755, 752, 746, 756, 755, 746, 742, 751, 751, 749, 752, 751, 756, 756, 755, 742, 749, 754, 749, 756, 753, 751, 754, 752, 751, 754, 753, 749, 755, 756]
C.ToArray 772.8 ms -> [766, 772, 755, 763, 758, 767, 763, 762, 761, 768, 769, 763, 770, 757, 765, 760, 766, 759, 764, 761, 760, 777, 1102, 881, 759, 765, 758, 762, 772, 761, 758, 757, 765, 769, 769, 761, 762, 762, 763, 760, 770, 764, 760, 768, 758, 766, 763, 770, 769, 761, 764, 761, 761, 767, 761, 762, 764, 757, 765, 766, 767, 771, 753, 762, 769, 768, 759, 764, 764, 760, 763, 763, 763, 763, 763, 767, 761, 771, 760, 765, 760, 758, 768, 770, 751, 771, 767, 771, 765, 763, 760, 765, 765, 769, 767, 767, 1193, 774, 767, 764]
S.ToList 704.73 ms -> [682, 708, 705, 699, 705, 704, 695, 703, 702, 699, 701, 708, 699, 702, 703, 701, 701, 699, 701, 707, 707, 700, 701, 705, 700, 697, 706, 702, 701, 706, 699, 692, 702, 697, 707, 704, 697, 698, 699, 699, 702, 703, 698, 697, 702, 703, 702, 704, 694, 697, 707, 695, 711, 710, 700, 693, 703, 699, 699, 706, 698, 701, 703, 704, 698, 706, 700, 704, 701, 699, 702, 705, 694, 698, 709, 736, 1053, 704, 694, 700, 698, 696, 701, 700, 700, 706, 706, 692, 698, 707, 703, 695, 703, 699, 694, 708, 695, 694, 706, 695]
S.ToArray 744.17 ms -> [746, 740, 725, 740, 739, 731, 746, 760, 735, 738, 740, 734, 744, 748, 737, 744, 745, 727, 736, 738, 728, 743, 745, 735, 748, 760, 739, 748, 762, 742, 741, 747, 733, 746, 758, 742, 742, 741, 724, 744, 747, 727, 740, 740, 729, 742, 757, 741, 740, 742, 726, 739, 746, 1133, 749, 737, 730, 740, 747, 733, 747, 752, 731, 747, 742, 730, 741, 749, 731, 749, 743, 730, 747, 742, 731, 737, 745, 734, 739, 735, 727, 743, 752, 731, 744, 742, 729, 740, 746, 731, 739, 746, 733, 745, 743, 733, 739, 742, 727, 737]

C1.ToList 31.71 ms -> [35, 32, 32, 30, 31, 33, 31, 32, 32, 31, 31, 32, 32, 33, 32, 31, 31, 32, 31, 32, 32, 32, 31, 32, 33, 32, 31, 31, 31, 32, 31, 34, 31, 31, 32, 33, 32, 32, 31, 32, 34, 32, 31, 32, 33, 31, 32, 32, 31, 32, 32, 32, 32, 32, 32, 31, 31, 32, 31, 33, 30, 31, 32, 30, 30, 33, 32, 32, 34, 31, 31, 31, 31, 32, 31, 31, 31, 31, 32, 31, 31, 33, 31, 32, 32, 32, 33, 32, 31, 31, 31, 31, 31, 32, 32, 33, 32, 31, 31, 32]
C1.ToArray 59.53 ms -> [63, 57, 58, 58, 57, 59, 59, 57, 60, 131, 127, 67, 58, 56, 59, 56, 57, 58, 58, 58, 57, 59, 60, 57, 57, 59, 60, 57, 57, 57, 58, 58, 58, 58, 57, 57, 61, 57, 58, 57, 57, 57, 57, 57, 58, 58, 58, 58, 57, 58, 59, 57, 58, 57, 57, 59, 58, 58, 59, 57, 59, 57, 56, 56, 59, 56, 56, 59, 57, 58, 58, 58, 57, 58, 59, 59, 58, 57, 58, 62, 65, 57, 57, 57, 58, 60, 59, 58, 59, 57, 58, 57, 58, 59, 58, 58, 58, 59, 60, 58]
S1.ToList 82.78 ms -> [87, 82, 83, 83, 82, 82, 83, 84, 82, 83, 84, 84, 84, 82, 82, 84, 82, 84, 83, 84, 82, 82, 82, 81, 83, 83, 83, 84, 84, 82, 82, 83, 83, 83, 82, 83, 85, 83, 82, 82, 84, 82, 82, 83, 83, 83, 82, 82, 82, 83, 82, 83, 82, 84, 82, 83, 82, 83, 82, 82, 82, 84, 82, 83, 82, 82, 86, 83, 83, 82, 83, 83, 83, 82, 84, 82, 83, 81, 82, 82, 82, 82, 83, 83, 83, 82, 83, 84, 83, 82, 83, 83, 83, 82, 83, 84, 82, 82, 83, 83]
S1.ToArray 122.3 ms -> [122, 119, 119, 120, 119, 120, 120, 121, 119, 119, 122, 120, 120, 120, 122, 120, 123, 120, 120, 120, 121, 123, 120, 120, 120, 121, 120, 121, 122, 120, 123, 119, 121, 118, 121, 120, 120, 120, 119, 124, 119, 121, 119, 120, 120, 120, 120, 120, 122, 121, 123, 230, 203, 123, 119, 119, 122, 119, 120, 120, 120, 122, 120, 121, 120, 121, 120, 121, 120, 121, 120, 120, 120, 121, 122, 121, 123, 119, 119, 119, 119, 121, 120, 120, 120, 122, 121, 122, 119, 120, 120, 121, 121, 120, 121, 120, 121, 118, 118, 118]

C2.ToList 3.43 ms -> [5, 3, 4, 4, 4, 3, 4, 4, 4, 4, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 4, 3, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 6, 4, 4, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 4, 4, 3, 4, 4, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3]
C2.ToArray 3.48 ms -> [3, 3, 3, 3, 4, 4, 3, 4, 4, 4, 3, 4, 3, 3, 4, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 3, 3, 4, 3, 3, 4, 4, 3, 3, 4, 3, 4, 4, 3, 4, 4, 4, 4, 4, 3, 3, 3, 4, 4, 3, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 4, 4, 4, 4, 3]
S2.ToList 41.47 ms -> [41, 41, 49, 67, 82, 41, 41, 40, 40, 40, 40, 40, 41, 40, 40, 40, 40, 40, 41, 40, 42, 42, 40, 40, 41, 41, 41, 40, 41, 40, 41, 40, 41, 40, 42, 41, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 42, 41, 41, 41, 42, 40, 41, 40, 40, 40, 42, 40, 41, 42, 41, 42, 41, 42, 40, 41, 41, 41, 41, 41, 41, 41, 41, 40, 41, 40, 41, 41, 41, 40, 41, 41, 40, 40, 41, 41, 41, 41, 41, 43, 40, 40, 41, 42, 41]
S2.ToArray 40.62 ms -> [42, 41, 44, 40, 40, 40, 40, 41, 41, 40, 41, 41, 41, 40, 41, 41, 40, 41, 41, 40, 41, 40, 40, 41, 42, 41, 41, 41, 40, 40, 40, 40, 40, 41, 41, 42, 40, 41, 41, 41, 41, 41, 40, 42, 40, 40, 41, 41, 41, 40, 41, 40, 40, 40, 40, 40, 41, 40, 40, 41, 40, 40, 40, 40, 41, 40, 41, 41, 41, 40, 41, 41, 40, 41, 40, 41, 42, 40, 41, 41, 42, 41, 41, 40, 41, 40, 41, 40, 41, 41, 40, 40, 40, 41, 41, 40, 40, 40, 40, 40]

В связи с ограничением на количество символов в стеке в ответе, образцы списков Group2 и Group3 опущены.

Как видите, использовать на самом деле не важToList илиToArry в большинстве случаев

Во время обработки вычисляется во время выполненияIEnumerable<T> объекты, если нагрузка, вызванная вычислениями, больше, чем выделение памяти и операции копированияToList а такжеToArray, несоответствие незначительно C.ToList vs C.ToArray а такжеS.ToList vs S.ToArray).

Разница может наблюдаться только для не-рассчитанных во время выполненияIEnumerable<T> только объекты C1.ToList vs C1.ToArray а такжеS1.ToList vs S1.ToArray). Но абсолютная разница (<60 мс) все еще приемлема для одного миллиона маленьких объектоIEnumerable<T>. На самом деле, разница определяется реализациейEnumerator<T> ofIEnumerable<T>. Итак, если ваша программа действительно очень чувствительна к этому, вы должныprofile, профиль, профиль! Наконец вы, вероятно, обнаружите, что узкое место не наToList илиToArray, но детализация счетчиков.

И результатC2.ToList vs C2.ToArray а такжеS2.ToList vs S2.ToArray показывает, что тебе действительно не нужно заботитьсяToList илиToArray не рассчитывается во время выполненияICollection<T> объекты

Конечно, это всего лишь результаты на моей машине, фактическое время, затраченное на выполнение этих операций на другой машине, не будет одинаковым, вы можете узнать на своей машине, используя приведенный выше код.

Единственная причина, по которой вам нужно сделать выбор, заключается в том, что у вас есть конкретные потребности вList<T> илиT[], как описано ответом@ Джеппе Стиг Нильсен.

Ваш ответ на вопрос