Это часть серии статей:

К списку()

Я очень часто нахожу использование ToList() в конце каждого запроса LINQ. В большинстве случаев в этом нет необходимости, и это может сильно повлиять на производительность.

Разберем небольшой пример:

Этот код записывает в консоль четные числа от 0 до 10. Вы можете увидеть в SharpLab.io, что это действительно работает.

Давайте расширим ToList() до эквивалентного кода:

ПРИМЕЧАНИЕ. В реализациях ToList() и ToArray() есть отличные оптимизации, но вы не узнаете, что их запускает, пока не заглянете в его код.

Проверьте в SharpLab.io, что результат такой же, но действительно ли это то, что вы хотели? ToList() скрывает дополнительное List<T> выделение, один foreach цикл и копию каждого элемента в списке.

Собственно, ToList() в этом случае не нужен ...

Проверьте в SharpLab.io, что код без ToList() выводит точно так же.

Представление

Выполнение кода в BenchmarkDotNet для диапазонов из 0, 500 и 1000 элементов дает следующие результаты.

ПРИМЕЧАНИЕ. Для теста я вычисляю общую сумму элементов последовательности вместо вывода на консоль.

ПРИМЕЧАНИЕ. ToArray() похож на ToList(), но возвращает массив вместо списка, и я включил его в тесты.

Неудивительно, что все реализации имеют O (n), что означает, что время обработки увеличивается с увеличением количества элементов в последовательности.

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

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

Преобразование требует выделения памяти в куче, при этом требуемый объем увеличивается непосредственно с количеством элементов в последовательности.

Выделение кучи вызовет сборку мусора. Если вы выделяете небольшие суммы и храните их в течение короткого периода времени, они будут обрабатываться коллекцией Gen 0, которая работает быстро, но не бесплатно. Если вы выделяете большой объем (›85 000 байт), они попадут непосредственно в LOH (кучу больших объектов), вызывая ее фрагментацию и замедляя ее.

Заключение

ToList() и ToArray() будут выделять динамическую память, чаще вызывая GC и рискуя получить OutOfMemoryException при масштабировании проекта.

Используйте их только в случае крайней необходимости!